diff --git a/src/audio.c b/src/audio.c index 3ee1fe81..c7104dbc 100644 --- a/src/audio.c +++ b/src/audio.c @@ -296,7 +296,7 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device *pDevice, mal_uint32 frameC (void)pDevice; // Mixing is basically just an accumulation. We need to initialize the output buffer to 0. - memset(pFramesOut, 0, frameCount*pDevice->channels*mal_get_sample_size_in_bytes(pDevice->format)); + memset(pFramesOut, 0, frameCount*pDevice->channels*mal_get_bytes_per_sample(pDevice->format)); // Using a mutex here for thread-safety which makes things not real-time. This is unlikely to be necessary for this project, but may // want to consider how you might want to avoid this. @@ -334,7 +334,7 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device *pDevice, mal_uint32 frameC // last few samples. bool flushDSP = !audioBuffer->looping; - mal_uint32 framesJustRead = mal_dsp_read_frames_ex(&audioBuffer->dsp, framesToReadRightNow, tempBuffer, flushDSP); + mal_uint32 framesJustRead = mal_dsp_read_ex(&audioBuffer->dsp, framesToReadRightNow, tempBuffer, flushDSP, audioBuffer->dsp.pUserData); if (framesJustRead > 0) { float *framesOut = (float *)pFramesOut + (framesRead*device.channels); @@ -394,7 +394,7 @@ static mal_uint32 OnAudioBufferDSPRead(mal_dsp *pDSP, mal_uint32 frameCount, voi isSubBufferProcessed[0] = audioBuffer->isSubBufferProcessed[0]; isSubBufferProcessed[1] = audioBuffer->isSubBufferProcessed[1]; - mal_uint32 frameSizeInBytes = mal_get_sample_size_in_bytes(audioBuffer->dsp.config.formatIn)*audioBuffer->dsp.config.channelsIn; + mal_uint32 frameSizeInBytes = mal_get_bytes_per_sample(audioBuffer->dsp.formatConverterIn.config.formatIn)*audioBuffer->dsp.formatConverterIn.config.channels; // Fill out every frame until we find a buffer that's marked as processed. Then fill the remainder with 0. mal_uint32 framesRead = 0; @@ -640,7 +640,7 @@ void SetMasterVolume(float volume) // Create a new audio buffer. Initially filled with silence AudioBuffer *CreateAudioBuffer(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 bufferSizeInFrames, AudioBufferUsage usage) { - AudioBuffer *audioBuffer = (AudioBuffer *)calloc(sizeof(*audioBuffer) + (bufferSizeInFrames*channels*mal_get_sample_size_in_bytes(format)), 1); + AudioBuffer *audioBuffer = (AudioBuffer *)calloc(sizeof(*audioBuffer) + (bufferSizeInFrames*channels*mal_get_bytes_per_sample(format)), 1); if (audioBuffer == NULL) { TraceLog(LOG_ERROR, "CreateAudioBuffer() : Failed to allocate memory for audio buffer"); @@ -656,7 +656,10 @@ AudioBuffer *CreateAudioBuffer(mal_format format, mal_uint32 channels, mal_uint3 dspConfig.channelsOut = DEVICE_CHANNELS; dspConfig.sampleRateIn = sampleRate; dspConfig.sampleRateOut = DEVICE_SAMPLE_RATE; - mal_result resultMAL = mal_dsp_init(&dspConfig, OnAudioBufferDSPRead, audioBuffer, &audioBuffer->dsp); + dspConfig.onRead = OnAudioBufferDSPRead; + dspConfig.pUserData = audioBuffer; + dspConfig.allowDynamicSampleRate = MAL_TRUE; // <-- Required for pitch shifting. + mal_result resultMAL = mal_dsp_init(&dspConfig, &audioBuffer->dsp); if (resultMAL != MAL_SUCCESS) { TraceLog(LOG_ERROR, "LoadSoundFromWave() : Failed to create data conversion pipeline"); @@ -791,7 +794,7 @@ void SetAudioBufferPitch(AudioBuffer *audioBuffer, float pitch) // Pitching is just an adjustment of the sample rate. Note that this changes the duration of the sound - higher pitches // will make the sound faster; lower pitches make it slower. - mal_uint32 newOutputSampleRate = (mal_uint32)((((float)audioBuffer->dsp.config.sampleRateOut / (float)audioBuffer->dsp.config.sampleRateIn) / pitch) * audioBuffer->dsp.config.sampleRateIn); + mal_uint32 newOutputSampleRate = (mal_uint32)((((float)audioBuffer->dsp.src.config.sampleRateOut / (float)audioBuffer->dsp.src.config.sampleRateIn) / pitch) * audioBuffer->dsp.src.config.sampleRateIn); mal_dsp_set_output_sample_rate(&audioBuffer->dsp, newOutputSampleRate); } @@ -913,7 +916,7 @@ Sound LoadSoundFromWave(Wave wave) AudioBuffer* audioBuffer = CreateAudioBuffer(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, frameCount, AUDIO_BUFFER_USAGE_STATIC); if (audioBuffer == NULL) TraceLog(LOG_WARNING, "LoadSoundFromWave() : Failed to create audio buffer"); - frameCount = mal_convert_frames(audioBuffer->buffer, audioBuffer->dsp.config.formatIn, audioBuffer->dsp.config.channelsIn, audioBuffer->dsp.config.sampleRateIn, wave.data, formatIn, wave.channels, wave.sampleRate, frameCountIn); + frameCount = mal_convert_frames(audioBuffer->buffer, audioBuffer->dsp.formatConverterIn.config.formatIn, audioBuffer->dsp.formatConverterIn.config.channels, audioBuffer->dsp.src.config.sampleRateIn, wave.data, formatIn, wave.channels, wave.sampleRate, frameCountIn); if (frameCount == 0) TraceLog(LOG_WARNING, "LoadSoundFromWave() : Format conversion failed"); sound.audioBuffer = audioBuffer; @@ -1015,7 +1018,7 @@ void UpdateSound(Sound sound, const void *data, int samplesCount) StopAudioBuffer(audioBuffer); // TODO: May want to lock/unlock this since this data buffer is read at mixing time. - memcpy(audioBuffer->buffer, data, samplesCount*audioBuffer->dsp.config.channelsIn*mal_get_sample_size_in_bytes(audioBuffer->dsp.config.formatIn)); + 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); diff --git a/src/external/dr_flac.h b/src/external/dr_flac.h index 60f57ece..ff23bdd1 100644 --- a/src/external/dr_flac.h +++ b/src/external/dr_flac.h @@ -1,5 +1,5 @@ // FLAC audio decoder. Public domain. See "unlicense" statement at the end of this file. -// dr_flac - v0.8d - 2017-09-22 +// dr_flac - v0.8g - 2018-04-19 // // David Reid - mackron@gmail.com @@ -106,6 +106,7 @@ // // // QUICK NOTES +// - dr_flac does not currently support changing the sample rate nor channel count mid stream. // - Audio data is output as signed 32-bit PCM, regardless of the bits per sample the FLAC stream is encoded as. // - This has not been tested on big-endian architectures. // - Rice codes in unencoded binary form (see https://xiph.org/flac/format.html#rice_partition) has not been tested. If anybody @@ -759,6 +760,8 @@ const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, dr #define DRFLAC_X64 #elif defined(__i386) || defined(_M_IX86) #define DRFLAC_X86 +#elif defined(__arm__) || defined(_M_ARM) +#define DRFLAC_ARM #endif // Compile-time CPU feature support. @@ -806,7 +809,7 @@ const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, dr #include #endif -#if defined(_MSC_VER) && _MSC_VER >= 1500 +#if defined(_MSC_VER) && _MSC_VER >= 1500 && (defined(DRFLAC_X86) || defined(DRFLAC_X64)) #define DRFLAC_HAS_LZCNT_INTRINSIC #elif (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) #define DRFLAC_HAS_LZCNT_INTRINSIC @@ -1694,7 +1697,7 @@ static drflac_bool32 drflac__find_and_seek_to_next_sync_code(drflac_bs* bs) #if !defined(DR_FLAC_NO_SIMD) && defined(DRFLAC_HAS_LZCNT_INTRINSIC) #define DRFLAC_IMPLEMENT_CLZ_LZCNT #endif -#if defined(_MSC_VER) && _MSC_VER >= 1400 +#if defined(_MSC_VER) && _MSC_VER >= 1400 && (defined(DRFLAC_X64) || defined(DRFLAC_X86)) #define DRFLAC_IMPLEMENT_CLZ_MSVC #endif @@ -2402,6 +2405,16 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ return DRFLAC_FALSE; } + // From the FLAC spec: + // The Rice partition order in a Rice-coded residual section must be less than or equal to 8. + if (partitionOrder > 8) { + return DRFLAC_FALSE; + } + + // Validation check. + if ((blockSize / (1 << partitionOrder)) <= order) { + return DRFLAC_FALSE; + } drflac_uint32 samplesInPartition = (blockSize / (1 << partitionOrder)) - order; drflac_uint32 partitionsRemaining = (1 << partitionOrder); @@ -2446,7 +2459,10 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ } partitionsRemaining -= 1; - samplesInPartition = blockSize / (1 << partitionOrder); + + if (partitionOrder != 0) { + samplesInPartition = blockSize / (1 << partitionOrder); + } } return DRFLAC_TRUE; @@ -2982,7 +2998,17 @@ static drflac_result drflac__decode_frame(drflac* pFlac) // This function should be called while the stream is sitting on the first byte after the frame header. drflac_zero_memory(pFlac->currentFrame.subframes, sizeof(pFlac->currentFrame.subframes)); + // The frame block size must never be larger than the maximum block size defined by the FLAC stream. + if (pFlac->currentFrame.header.blockSize > pFlac->maxBlockSize) { + return DRFLAC_ERROR; + } + + // The number of channels in the frame must match the channel count from the STREAMINFO block. int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); + if (channelCount != (int)pFlac->channels) { + return DRFLAC_ERROR; + } + for (int i = 0; i < channelCount; ++i) { if (!drflac__decode_subframe(&pFlac->bs, &pFlac->currentFrame, i, pFlac->pDecodedSamples + (pFlac->currentFrame.header.blockSize * i))) { return DRFLAC_ERROR; @@ -5498,6 +5524,16 @@ const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, dr // REVISION HISTORY // +// v0.8g - 2018-04-19 +// - Fix build on non-x86/x64 architectures. +// +// v0.8f - 2018-02-02 +// - Stop pretending to support changing rate/channels mid stream. +// +// v0.8e - 2018-02-01 +// - Fix a crash when the block size of a frame is larger than the maximum block size defined by the FLAC stream. +// - Fix a crash the the Rice partition order is invalid. +// // v0.8d - 2017-09-22 // - Add support for decoding streams with ID3 tags. ID3 tags are just skipped. // diff --git a/src/external/dr_wav.h b/src/external/dr_wav.h index 536df8f3..f11dc2a8 100644 --- a/src/external/dr_wav.h +++ b/src/external/dr_wav.h @@ -1,5 +1,5 @@ // WAV audio loader and writer. Public domain. See "unlicense" statement at the end of this file. -// dr_wav - v0.7a - 2017-11-17 +// dr_wav - v0.7f - 2018-02-05 // // David Reid - mackron@gmail.com @@ -103,8 +103,8 @@ // - Signed 16-bit PCM // - Signed 24-bit PCM // - Signed 32-bit PCM -// - IEEE 32-bit floating point. -// - IEEE 64-bit floating point. +// - IEEE 32-bit floating point +// - IEEE 64-bit floating point // - A-law and u-law // - Microsoft ADPCM // - IMA ADPCM (DVI, format code 0x11) @@ -643,13 +643,13 @@ drwav_int16* drwav_open_and_read_s16(drwav_read_proc onRead, drwav_seek_proc onS float* drwav_open_and_read_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); drwav_int32* drwav_open_and_read_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); #ifndef DR_WAV_NO_STDIO -// Opens an decodes a wav file in a single operation. +// Opens and decodes a wav file in a single operation. drwav_int16* drwav_open_and_read_file_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); float* drwav_open_and_read_file_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); drwav_int32* drwav_open_and_read_file_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); #endif -// Opens an decodes a wav file from a block of memory in a single operation. +// Opens and decodes a wav file from a block of memory in a single operation. drwav_int16* drwav_open_and_read_memory_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); float* drwav_open_and_read_memory_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); drwav_int32* drwav_open_and_read_memory_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); @@ -820,6 +820,9 @@ static DRWAV_INLINE drwav_bool32 drwav__is_compressed_format_tag(drwav_uint16 fo } +drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +drwav_uint64 drwav_read_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); + typedef struct { union @@ -1395,6 +1398,11 @@ drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onS return DRWAV_FALSE; // Failed to read the "fmt " chunk. } + // Basic validation. + if (fmt.sampleRate == 0 || fmt.channels == 0 || fmt.bitsPerSample == 0 || fmt.blockAlign == 0) { + return DRWAV_FALSE; // Invalid channel count. Probably an invalid WAV file. + } + // Translate the internal format. unsigned short translatedFormatTag = fmt.formatTag; @@ -1470,11 +1478,16 @@ drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onS pWav->sampleRate = fmt.sampleRate; pWav->channels = fmt.channels; pWav->bitsPerSample = fmt.bitsPerSample; - pWav->bytesPerSample = (unsigned int)(fmt.blockAlign / fmt.channels); + pWav->bytesPerSample = fmt.blockAlign / fmt.channels; pWav->bytesRemaining = dataSize; pWav->translatedFormatTag = translatedFormatTag; pWav->dataChunkDataSize = dataSize; + // The bytes per sample should never be 0 at this point. This would indicate an invalid WAV file. + if (pWav->bytesPerSample == 0) { + return DRWAV_FALSE; + } + if (sampleCountFromFactChunk != 0) { pWav->totalSampleCount = sampleCountFromFactChunk * fmt.channels; } else { @@ -1489,18 +1502,26 @@ drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onS pWav->totalSampleCount = ((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels); } } - - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + + // The way we calculate the bytes per sample does not make sense for compressed formats so we just set it to 0. + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { pWav->bytesPerSample = 0; } + // Some formats only support a certain number of channels. + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + if (pWav->channels > 2) { + return DRWAV_FALSE; + } + } + #ifdef DR_WAV_LIBSNDFILE_COMPAT // I use libsndfile as a benchmark for testing, however in the version I'm using (from the Windows installer on the libsndfile website), // it appears the total sample count libsndfile uses for MS-ADPCM is incorrect. It would seem they are computing the total sample count // from the number of blocks, however this results in the inclusion of the extra silent samples at the end of the last block. The correct // way to know the total sample count is to inspect the "fact" chunk which should always be present for compressed formats, and should // always include the sample count. This little block of code below is only used to emulate the libsndfile logic so I can properly run my - // correctness tests against libsndfile and is disabled by default. + // correctness tests against libsndfile, and is disabled by default. if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { drwav_uint64 blockCount = dataSize / fmt.blockAlign; pWav->totalSampleCount = (blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2; // x2 because two samples per byte. @@ -1798,27 +1819,40 @@ drwav_bool32 drwav_seek_to_sample(drwav* pWav, drwav_uint64 sample) // to seek back to the start. if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { // TODO: This can be optimized. + + // If we're seeking forward it's simple - just keep reading samples until we hit the sample we're requesting. If we're seeking backwards, + // we first need to seek back to the start and then just do the same thing as a forward seek. + if (sample < pWav->compressed.iCurrentSample) { + if (!drwav_seek_to_first_sample(pWav)) { + return DRWAV_FALSE; + } + } + if (sample > pWav->compressed.iCurrentSample) { - // Seeking forward - just move from the current position. drwav_uint64 offset = sample - pWav->compressed.iCurrentSample; drwav_int16 devnull[2048]; while (offset > 0) { - drwav_uint64 samplesToRead = sample; + drwav_uint64 samplesToRead = offset; if (samplesToRead > 2048) { samplesToRead = 2048; } - drwav_uint64 samplesRead = drwav_read_s16(pWav, samplesToRead, devnull); + drwav_uint64 samplesRead = 0; + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + samplesRead = drwav_read_s16__msadpcm(pWav, samplesToRead, devnull); + } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + samplesRead = drwav_read_s16__ima(pWav, samplesToRead, devnull); + } else { + assert(DRWAV_FALSE); // If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. + } + if (samplesRead != samplesToRead) { return DRWAV_FALSE; } offset -= samplesRead; } - } else { - // Seeking backwards. Just use the fallback. - goto fallback; } } else { drwav_uint64 totalSizeInBytes = pWav->totalSampleCount * pWav->bytesPerSample; @@ -1850,30 +1884,6 @@ drwav_bool32 drwav_seek_to_sample(drwav* pWav, drwav_uint64 sample) } } - return DRWAV_TRUE; - -fallback: - // This is a generic seek implementation that just continuously reads samples into a temporary buffer. This should work for all supported - // formats, but it is not efficient. This should be used as a fall back. - if (!drwav_seek_to_first_sample(pWav)) { - return DRWAV_FALSE; - } - - drwav_int16 devnull[2048]; - while (sample > 0) { - drwav_uint64 samplesToRead = sample; - if (samplesToRead > 2048) { - samplesToRead = 2048; - } - - drwav_uint64 samplesRead = drwav_read_s16(pWav, samplesToRead, devnull); - if (samplesRead != samplesToRead) { - return DRWAV_FALSE; - } - - sample -= samplesRead; - } - return DRWAV_TRUE; } @@ -1906,132 +1916,6 @@ drwav_uint64 drwav_write(drwav* pWav, drwav_uint64 samplesToWrite, const void* p } -#ifndef DR_WAV_NO_CONVERSION_API -static unsigned short g_drwavAlawTable[256] = { - 0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580, - 0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0, - 0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600, - 0xD500, 0xD700, 0xD100, 0xD300, 0xDD00, 0xDF00, 0xD900, 0xDB00, 0xC500, 0xC700, 0xC100, 0xC300, 0xCD00, 0xCF00, 0xC900, 0xCB00, - 0xFEA8, 0xFEB8, 0xFE88, 0xFE98, 0xFEE8, 0xFEF8, 0xFEC8, 0xFED8, 0xFE28, 0xFE38, 0xFE08, 0xFE18, 0xFE68, 0xFE78, 0xFE48, 0xFE58, - 0xFFA8, 0xFFB8, 0xFF88, 0xFF98, 0xFFE8, 0xFFF8, 0xFFC8, 0xFFD8, 0xFF28, 0xFF38, 0xFF08, 0xFF18, 0xFF68, 0xFF78, 0xFF48, 0xFF58, - 0xFAA0, 0xFAE0, 0xFA20, 0xFA60, 0xFBA0, 0xFBE0, 0xFB20, 0xFB60, 0xF8A0, 0xF8E0, 0xF820, 0xF860, 0xF9A0, 0xF9E0, 0xF920, 0xF960, - 0xFD50, 0xFD70, 0xFD10, 0xFD30, 0xFDD0, 0xFDF0, 0xFD90, 0xFDB0, 0xFC50, 0xFC70, 0xFC10, 0xFC30, 0xFCD0, 0xFCF0, 0xFC90, 0xFCB0, - 0x1580, 0x1480, 0x1780, 0x1680, 0x1180, 0x1080, 0x1380, 0x1280, 0x1D80, 0x1C80, 0x1F80, 0x1E80, 0x1980, 0x1880, 0x1B80, 0x1A80, - 0x0AC0, 0x0A40, 0x0BC0, 0x0B40, 0x08C0, 0x0840, 0x09C0, 0x0940, 0x0EC0, 0x0E40, 0x0FC0, 0x0F40, 0x0CC0, 0x0C40, 0x0DC0, 0x0D40, - 0x5600, 0x5200, 0x5E00, 0x5A00, 0x4600, 0x4200, 0x4E00, 0x4A00, 0x7600, 0x7200, 0x7E00, 0x7A00, 0x6600, 0x6200, 0x6E00, 0x6A00, - 0x2B00, 0x2900, 0x2F00, 0x2D00, 0x2300, 0x2100, 0x2700, 0x2500, 0x3B00, 0x3900, 0x3F00, 0x3D00, 0x3300, 0x3100, 0x3700, 0x3500, - 0x0158, 0x0148, 0x0178, 0x0168, 0x0118, 0x0108, 0x0138, 0x0128, 0x01D8, 0x01C8, 0x01F8, 0x01E8, 0x0198, 0x0188, 0x01B8, 0x01A8, - 0x0058, 0x0048, 0x0078, 0x0068, 0x0018, 0x0008, 0x0038, 0x0028, 0x00D8, 0x00C8, 0x00F8, 0x00E8, 0x0098, 0x0088, 0x00B8, 0x00A8, - 0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0, - 0x02B0, 0x0290, 0x02F0, 0x02D0, 0x0230, 0x0210, 0x0270, 0x0250, 0x03B0, 0x0390, 0x03F0, 0x03D0, 0x0330, 0x0310, 0x0370, 0x0350 -}; - -static unsigned short g_drwavMulawTable[256] = { - 0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84, - 0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84, - 0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004, - 0xF0C4, 0xF144, 0xF1C4, 0xF244, 0xF2C4, 0xF344, 0xF3C4, 0xF444, 0xF4C4, 0xF544, 0xF5C4, 0xF644, 0xF6C4, 0xF744, 0xF7C4, 0xF844, - 0xF8A4, 0xF8E4, 0xF924, 0xF964, 0xF9A4, 0xF9E4, 0xFA24, 0xFA64, 0xFAA4, 0xFAE4, 0xFB24, 0xFB64, 0xFBA4, 0xFBE4, 0xFC24, 0xFC64, - 0xFC94, 0xFCB4, 0xFCD4, 0xFCF4, 0xFD14, 0xFD34, 0xFD54, 0xFD74, 0xFD94, 0xFDB4, 0xFDD4, 0xFDF4, 0xFE14, 0xFE34, 0xFE54, 0xFE74, - 0xFE8C, 0xFE9C, 0xFEAC, 0xFEBC, 0xFECC, 0xFEDC, 0xFEEC, 0xFEFC, 0xFF0C, 0xFF1C, 0xFF2C, 0xFF3C, 0xFF4C, 0xFF5C, 0xFF6C, 0xFF7C, - 0xFF88, 0xFF90, 0xFF98, 0xFFA0, 0xFFA8, 0xFFB0, 0xFFB8, 0xFFC0, 0xFFC8, 0xFFD0, 0xFFD8, 0xFFE0, 0xFFE8, 0xFFF0, 0xFFF8, 0x0000, - 0x7D7C, 0x797C, 0x757C, 0x717C, 0x6D7C, 0x697C, 0x657C, 0x617C, 0x5D7C, 0x597C, 0x557C, 0x517C, 0x4D7C, 0x497C, 0x457C, 0x417C, - 0x3E7C, 0x3C7C, 0x3A7C, 0x387C, 0x367C, 0x347C, 0x327C, 0x307C, 0x2E7C, 0x2C7C, 0x2A7C, 0x287C, 0x267C, 0x247C, 0x227C, 0x207C, - 0x1EFC, 0x1DFC, 0x1CFC, 0x1BFC, 0x1AFC, 0x19FC, 0x18FC, 0x17FC, 0x16FC, 0x15FC, 0x14FC, 0x13FC, 0x12FC, 0x11FC, 0x10FC, 0x0FFC, - 0x0F3C, 0x0EBC, 0x0E3C, 0x0DBC, 0x0D3C, 0x0CBC, 0x0C3C, 0x0BBC, 0x0B3C, 0x0ABC, 0x0A3C, 0x09BC, 0x093C, 0x08BC, 0x083C, 0x07BC, - 0x075C, 0x071C, 0x06DC, 0x069C, 0x065C, 0x061C, 0x05DC, 0x059C, 0x055C, 0x051C, 0x04DC, 0x049C, 0x045C, 0x041C, 0x03DC, 0x039C, - 0x036C, 0x034C, 0x032C, 0x030C, 0x02EC, 0x02CC, 0x02AC, 0x028C, 0x026C, 0x024C, 0x022C, 0x020C, 0x01EC, 0x01CC, 0x01AC, 0x018C, - 0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, 0x00F4, 0x00E4, 0x00D4, 0x00C4, 0x00B4, 0x00A4, 0x0094, 0x0084, - 0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000 -}; - -static DRWAV_INLINE drwav_int16 drwav__alaw_to_s16(drwav_uint8 sampleIn) -{ - return (short)g_drwavAlawTable[sampleIn]; -} - -static DRWAV_INLINE drwav_int16 drwav__mulaw_to_s16(drwav_uint8 sampleIn) -{ - return (short)g_drwavMulawTable[sampleIn]; -} - - - -static void drwav__pcm_to_s16(drwav_int16* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned short bytesPerSample) -{ - // Special case for 8-bit sample data because it's treated as unsigned. - if (bytesPerSample == 1) { - drwav_u8_to_s16(pOut, pIn, totalSampleCount); - return; - } - - - // Slightly more optimal implementation for common formats. - if (bytesPerSample == 2) { - for (unsigned int i = 0; i < totalSampleCount; ++i) { - *pOut++ = ((drwav_int16*)pIn)[i]; - } - return; - } - if (bytesPerSample == 3) { - drwav_s24_to_s16(pOut, pIn, totalSampleCount); - return; - } - if (bytesPerSample == 4) { - drwav_s32_to_s16(pOut, (const drwav_int32*)pIn, totalSampleCount); - return; - } - - - // Generic, slow converter. - for (unsigned int i = 0; i < totalSampleCount; ++i) { - unsigned short sample = 0; - unsigned short shift = (8 - bytesPerSample) * 8; - for (unsigned short j = 0; j < bytesPerSample && j < 2; ++j) { - sample |= (unsigned short)(pIn[j]) << shift; - shift += 8; - } - - pIn += bytesPerSample; - *pOut++ = sample; - } -} - -static void drwav__ieee_to_s16(drwav_int16* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned short bytesPerSample) -{ - if (bytesPerSample == 4) { - drwav_f32_to_s16(pOut, (float*)pIn, totalSampleCount); - return; - } else { - drwav_f64_to_s16(pOut, (double*)pIn, totalSampleCount); - return; - } -} - -drwav_uint64 drwav_read_s16__pcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) -{ - // Fast path. - if (pWav->bytesPerSample == 2) { - return drwav_read(pWav, samplesToRead, pBufferOut); - } - - drwav_uint64 totalSamplesRead = 0; - unsigned char sampleData[4096]; - while (samplesToRead > 0) { - drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); - if (samplesRead == 0) { - break; - } - - drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample); - - pBufferOut += samplesRead; - samplesToRead -= samplesRead; - totalSamplesRead += samplesRead; - } - - return totalSamplesRead; -} drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) { @@ -2334,6 +2218,147 @@ drwav_uint64 drwav_read_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_ return totalSamplesRead; } + +#ifndef DR_WAV_NO_CONVERSION_API +static unsigned short g_drwavAlawTable[256] = { + 0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580, + 0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0, + 0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600, + 0xD500, 0xD700, 0xD100, 0xD300, 0xDD00, 0xDF00, 0xD900, 0xDB00, 0xC500, 0xC700, 0xC100, 0xC300, 0xCD00, 0xCF00, 0xC900, 0xCB00, + 0xFEA8, 0xFEB8, 0xFE88, 0xFE98, 0xFEE8, 0xFEF8, 0xFEC8, 0xFED8, 0xFE28, 0xFE38, 0xFE08, 0xFE18, 0xFE68, 0xFE78, 0xFE48, 0xFE58, + 0xFFA8, 0xFFB8, 0xFF88, 0xFF98, 0xFFE8, 0xFFF8, 0xFFC8, 0xFFD8, 0xFF28, 0xFF38, 0xFF08, 0xFF18, 0xFF68, 0xFF78, 0xFF48, 0xFF58, + 0xFAA0, 0xFAE0, 0xFA20, 0xFA60, 0xFBA0, 0xFBE0, 0xFB20, 0xFB60, 0xF8A0, 0xF8E0, 0xF820, 0xF860, 0xF9A0, 0xF9E0, 0xF920, 0xF960, + 0xFD50, 0xFD70, 0xFD10, 0xFD30, 0xFDD0, 0xFDF0, 0xFD90, 0xFDB0, 0xFC50, 0xFC70, 0xFC10, 0xFC30, 0xFCD0, 0xFCF0, 0xFC90, 0xFCB0, + 0x1580, 0x1480, 0x1780, 0x1680, 0x1180, 0x1080, 0x1380, 0x1280, 0x1D80, 0x1C80, 0x1F80, 0x1E80, 0x1980, 0x1880, 0x1B80, 0x1A80, + 0x0AC0, 0x0A40, 0x0BC0, 0x0B40, 0x08C0, 0x0840, 0x09C0, 0x0940, 0x0EC0, 0x0E40, 0x0FC0, 0x0F40, 0x0CC0, 0x0C40, 0x0DC0, 0x0D40, + 0x5600, 0x5200, 0x5E00, 0x5A00, 0x4600, 0x4200, 0x4E00, 0x4A00, 0x7600, 0x7200, 0x7E00, 0x7A00, 0x6600, 0x6200, 0x6E00, 0x6A00, + 0x2B00, 0x2900, 0x2F00, 0x2D00, 0x2300, 0x2100, 0x2700, 0x2500, 0x3B00, 0x3900, 0x3F00, 0x3D00, 0x3300, 0x3100, 0x3700, 0x3500, + 0x0158, 0x0148, 0x0178, 0x0168, 0x0118, 0x0108, 0x0138, 0x0128, 0x01D8, 0x01C8, 0x01F8, 0x01E8, 0x0198, 0x0188, 0x01B8, 0x01A8, + 0x0058, 0x0048, 0x0078, 0x0068, 0x0018, 0x0008, 0x0038, 0x0028, 0x00D8, 0x00C8, 0x00F8, 0x00E8, 0x0098, 0x0088, 0x00B8, 0x00A8, + 0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0, + 0x02B0, 0x0290, 0x02F0, 0x02D0, 0x0230, 0x0210, 0x0270, 0x0250, 0x03B0, 0x0390, 0x03F0, 0x03D0, 0x0330, 0x0310, 0x0370, 0x0350 +}; + +static unsigned short g_drwavMulawTable[256] = { + 0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84, + 0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84, + 0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004, + 0xF0C4, 0xF144, 0xF1C4, 0xF244, 0xF2C4, 0xF344, 0xF3C4, 0xF444, 0xF4C4, 0xF544, 0xF5C4, 0xF644, 0xF6C4, 0xF744, 0xF7C4, 0xF844, + 0xF8A4, 0xF8E4, 0xF924, 0xF964, 0xF9A4, 0xF9E4, 0xFA24, 0xFA64, 0xFAA4, 0xFAE4, 0xFB24, 0xFB64, 0xFBA4, 0xFBE4, 0xFC24, 0xFC64, + 0xFC94, 0xFCB4, 0xFCD4, 0xFCF4, 0xFD14, 0xFD34, 0xFD54, 0xFD74, 0xFD94, 0xFDB4, 0xFDD4, 0xFDF4, 0xFE14, 0xFE34, 0xFE54, 0xFE74, + 0xFE8C, 0xFE9C, 0xFEAC, 0xFEBC, 0xFECC, 0xFEDC, 0xFEEC, 0xFEFC, 0xFF0C, 0xFF1C, 0xFF2C, 0xFF3C, 0xFF4C, 0xFF5C, 0xFF6C, 0xFF7C, + 0xFF88, 0xFF90, 0xFF98, 0xFFA0, 0xFFA8, 0xFFB0, 0xFFB8, 0xFFC0, 0xFFC8, 0xFFD0, 0xFFD8, 0xFFE0, 0xFFE8, 0xFFF0, 0xFFF8, 0x0000, + 0x7D7C, 0x797C, 0x757C, 0x717C, 0x6D7C, 0x697C, 0x657C, 0x617C, 0x5D7C, 0x597C, 0x557C, 0x517C, 0x4D7C, 0x497C, 0x457C, 0x417C, + 0x3E7C, 0x3C7C, 0x3A7C, 0x387C, 0x367C, 0x347C, 0x327C, 0x307C, 0x2E7C, 0x2C7C, 0x2A7C, 0x287C, 0x267C, 0x247C, 0x227C, 0x207C, + 0x1EFC, 0x1DFC, 0x1CFC, 0x1BFC, 0x1AFC, 0x19FC, 0x18FC, 0x17FC, 0x16FC, 0x15FC, 0x14FC, 0x13FC, 0x12FC, 0x11FC, 0x10FC, 0x0FFC, + 0x0F3C, 0x0EBC, 0x0E3C, 0x0DBC, 0x0D3C, 0x0CBC, 0x0C3C, 0x0BBC, 0x0B3C, 0x0ABC, 0x0A3C, 0x09BC, 0x093C, 0x08BC, 0x083C, 0x07BC, + 0x075C, 0x071C, 0x06DC, 0x069C, 0x065C, 0x061C, 0x05DC, 0x059C, 0x055C, 0x051C, 0x04DC, 0x049C, 0x045C, 0x041C, 0x03DC, 0x039C, + 0x036C, 0x034C, 0x032C, 0x030C, 0x02EC, 0x02CC, 0x02AC, 0x028C, 0x026C, 0x024C, 0x022C, 0x020C, 0x01EC, 0x01CC, 0x01AC, 0x018C, + 0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, 0x00F4, 0x00E4, 0x00D4, 0x00C4, 0x00B4, 0x00A4, 0x0094, 0x0084, + 0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000 +}; + +static DRWAV_INLINE drwav_int16 drwav__alaw_to_s16(drwav_uint8 sampleIn) +{ + return (short)g_drwavAlawTable[sampleIn]; +} + +static DRWAV_INLINE drwav_int16 drwav__mulaw_to_s16(drwav_uint8 sampleIn) +{ + return (short)g_drwavMulawTable[sampleIn]; +} + + + +static void drwav__pcm_to_s16(drwav_int16* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned short bytesPerSample) +{ + // Special case for 8-bit sample data because it's treated as unsigned. + if (bytesPerSample == 1) { + drwav_u8_to_s16(pOut, pIn, totalSampleCount); + return; + } + + + // Slightly more optimal implementation for common formats. + if (bytesPerSample == 2) { + for (unsigned int i = 0; i < totalSampleCount; ++i) { + *pOut++ = ((drwav_int16*)pIn)[i]; + } + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_s16(pOut, pIn, totalSampleCount); + return; + } + if (bytesPerSample == 4) { + drwav_s32_to_s16(pOut, (const drwav_int32*)pIn, totalSampleCount); + return; + } + + + // Anything more than 64 bits per sample is not supported. + if (bytesPerSample > 8) { + drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); + return; + } + + + // Generic, slow converter. + for (unsigned int i = 0; i < totalSampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample && j < 8; j += 1) { + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (drwav_int16)((drwav_int64)sample >> 48); + } +} + +static void drwav__ieee_to_s16(drwav_int16* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned short bytesPerSample) +{ + if (bytesPerSample == 4) { + drwav_f32_to_s16(pOut, (float*)pIn, totalSampleCount); + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_s16(pOut, (double*)pIn, totalSampleCount); + return; + } else { + // Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. + drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); + return; + } +} + +drwav_uint64 drwav_read_s16__pcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) +{ + // Fast path. + if (pWav->bytesPerSample == 2) { + return drwav_read(pWav, samplesToRead, pBufferOut); + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + drwav_uint64 drwav_read_s16__ieee(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalSamplesRead = 0; @@ -2529,17 +2554,27 @@ static void drwav__pcm_to_f32(float* pOut, const unsigned char* pIn, size_t samp return; } + + // Anything more than 64 bits per sample is not supported. + if (bytesPerSample > 8) { + drwav_zero_memory(pOut, sampleCount * sizeof(*pOut)); + return; + } + + // Generic, slow converter. for (unsigned int i = 0; i < sampleCount; ++i) { - unsigned int sample = 0; + drwav_uint64 sample = 0; unsigned int shift = (8 - bytesPerSample) * 8; - for (unsigned short j = 0; j < bytesPerSample && j < 4; ++j) { - sample |= (unsigned int)(pIn[j]) << shift; + + unsigned int j; + for (j = 0; j < bytesPerSample && j < 8; j += 1) { + sample |= (drwav_uint64)(pIn[j]) << shift; shift += 8; } - pIn += bytesPerSample; - *pOut++ = (float)((int)sample / 2147483648.0); + pIn += j; + *pOut++ = (float)((drwav_int64)sample / 9223372036854775807.0); } } @@ -2550,15 +2585,23 @@ static void drwav__ieee_to_f32(float* pOut, const unsigned char* pIn, size_t sam *pOut++ = ((float*)pIn)[i]; } return; - } else { + } else if (bytesPerSample == 8) { drwav_f64_to_f32(pOut, (double*)pIn, sampleCount); return; + } else { + // Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. + drwav_zero_memory(pOut, sampleCount * sizeof(*pOut)); + return; } } drwav_uint64 drwav_read_f32__pcm(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) { + if (pWav->bytesPerSample == 0) { + return 0; + } + drwav_uint64 totalSamplesRead = 0; unsigned char sampleData[4096]; while (samplesToRead > 0) { @@ -2628,6 +2671,10 @@ drwav_uint64 drwav_read_f32__ieee(drwav* pWav, drwav_uint64 samplesToRead, float return drwav_read(pWav, samplesToRead, pBufferOut); } + if (pWav->bytesPerSample == 0) { + return 0; + } + drwav_uint64 totalSamplesRead = 0; unsigned char sampleData[4096]; while (samplesToRead > 0) { @@ -2648,6 +2695,10 @@ drwav_uint64 drwav_read_f32__ieee(drwav* pWav, drwav_uint64 samplesToRead, float drwav_uint64 drwav_read_f32__alaw(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) { + if (pWav->bytesPerSample == 0) { + return 0; + } + drwav_uint64 totalSamplesRead = 0; unsigned char sampleData[4096]; while (samplesToRead > 0) { @@ -2668,6 +2719,10 @@ drwav_uint64 drwav_read_f32__alaw(drwav* pWav, drwav_uint64 samplesToRead, float drwav_uint64 drwav_read_f32__mulaw(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) { + if (pWav->bytesPerSample == 0) { + return 0; + } + drwav_uint64 totalSamplesRead = 0; unsigned char sampleData[4096]; while (samplesToRead > 0) { @@ -2842,17 +2897,27 @@ static void drwav__pcm_to_s32(drwav_int32* pOut, const unsigned char* pIn, size_ return; } + + // Anything more than 64 bits per sample is not supported. + if (bytesPerSample > 8) { + drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); + return; + } + + // Generic, slow converter. for (unsigned int i = 0; i < totalSampleCount; ++i) { - unsigned int sample = 0; + drwav_uint64 sample = 0; unsigned int shift = (8 - bytesPerSample) * 8; - for (unsigned short j = 0; j < bytesPerSample && j < 4; ++j) { - sample |= (unsigned int)(pIn[j]) << shift; + + unsigned int j; + for (j = 0; j < bytesPerSample && j < 8; j += 1) { + sample |= (drwav_uint64)(pIn[j]) << shift; shift += 8; } - pIn += bytesPerSample; - *pOut++ = sample; + pIn += j; + *pOut++ = (drwav_int32)((drwav_int64)sample >> 32); } } @@ -2861,9 +2926,13 @@ static void drwav__ieee_to_s32(drwav_int32* pOut, const unsigned char* pIn, size if (bytesPerSample == 4) { drwav_f32_to_s32(pOut, (float*)pIn, totalSampleCount); return; - } else { + } else if (bytesPerSample == 8) { drwav_f64_to_s32(pOut, (double*)pIn, totalSampleCount); return; + } else { + // Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. + drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); + return; } } @@ -2875,6 +2944,10 @@ drwav_uint64 drwav_read_s32__pcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_ return drwav_read(pWav, samplesToRead, pBufferOut); } + if (pWav->bytesPerSample == 0) { + return 0; + } + drwav_uint64 totalSamplesRead = 0; unsigned char sampleData[4096]; while (samplesToRead > 0) { @@ -2939,6 +3012,10 @@ drwav_uint64 drwav_read_s32__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_ drwav_uint64 drwav_read_s32__ieee(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) { + if (pWav->bytesPerSample == 0) { + return 0; + } + drwav_uint64 totalSamplesRead = 0; unsigned char sampleData[4096]; while (samplesToRead > 0) { @@ -2959,6 +3036,10 @@ drwav_uint64 drwav_read_s32__ieee(drwav* pWav, drwav_uint64 samplesToRead, drwav drwav_uint64 drwav_read_s32__alaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) { + if (pWav->bytesPerSample == 0) { + return 0; + } + drwav_uint64 totalSamplesRead = 0; unsigned char sampleData[4096]; while (samplesToRead > 0) { @@ -2979,6 +3060,10 @@ drwav_uint64 drwav_read_s32__alaw(drwav* pWav, drwav_uint64 samplesToRead, drwav drwav_uint64 drwav_read_s32__mulaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) { + if (pWav->bytesPerSample == 0) { + return 0; + } + drwav_uint64 totalSamplesRead = 0; unsigned char sampleData[4096]; while (samplesToRead > 0) { @@ -3354,6 +3439,25 @@ void drwav_free(void* pDataReturnedByOpenAndRead) // REVISION HISTORY // +// v0.7f - 2018-02-05 +// - Restrict ADPCM formats to a maximum of 2 channels. +// +// v0.7e - 2018-02-02 +// - Fix a crash. +// +// v0.7d - 2018-02-01 +// - Fix a crash. +// +// v0.7c - 2018-02-01 +// - Set drwav.bytesPerSample to 0 for all compressed formats. +// - Fix a crash when reading 16-bit floating point WAV files. In this case dr_wav will output silence for +// all format conversion reading APIs (*_s16, *_s32, *_f32 APIs). +// - Fix some divide-by-zero errors. +// +// v0.7b - 2018-01-22 +// - Fix errors with seeking of compressed formats. +// - Fix compilation error when DR_WAV_NO_CONVERSION_API +// // v0.7a - 2017-11-17 // - Fix some GCC warnings. // @@ -3393,7 +3497,7 @@ void drwav_free(void* pDataReturnedByOpenAndRead) // // v0.5 - 2016-09-29 // - API CHANGE. Swap the order of "channels" and "sampleRate" parameters in drwav_open_and_read*(). Rationale for this is to -// keep it consistent with dr_audio and drwav_flac. +// keep it consistent with dr_audio and dr_flac. // // v0.4b - 2016-09-18 // - Fixed a typo in documentation. @@ -3403,8 +3507,8 @@ void drwav_free(void* pDataReturnedByOpenAndRead) // - Change date format to ISO 8601 (YYYY-MM-DD) // // v0.4 - 2016-07-13 -// - API CHANGE. Make onSeek consistent with drwav_flac. -// - API CHANGE. Rename drwav_seek() to drwav_seek_to_sample() for clarity and consistency with drwav_flac. +// - API CHANGE. Make onSeek consistent with dr_flac. +// - API CHANGE. Rename drwav_seek() to drwav_seek_to_sample() for clarity and consistency with dr_flac. // - Added support for Sony Wave64. // // v0.3a - 2016-05-28 @@ -3418,7 +3522,7 @@ void drwav_free(void* pDataReturnedByOpenAndRead) // - Fixed Linux/GCC build. // // v0.2 - 2016-05-11 -// - Added support for reading data as signed 32-bit PCM for consistency with drwav_flac. +// - Added support for reading data as signed 32-bit PCM for consistency with dr_flac. // // v0.1a - 2016-05-07 // - Fixed a bug in drwav_open_file() where the file handle would not be closed if the loader failed to initialize. diff --git a/src/external/mini_al.h b/src/external/mini_al.h index d69abe41..5a6ef491 100644 --- a/src/external/mini_al.h +++ b/src/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.6b - 2018-02-03 +// mini_al - v0.x - 2018-xx-xx // // David Reid - davidreidsoftware@gmail.com @@ -17,6 +17,8 @@ // - DirectSound // - WinMM // - ALSA +// - PulseAudio +// - JACK // - OSS // - OpenSL|ES / Android // - OpenAL @@ -41,31 +43,37 @@ // // You can then #include this file in other parts of the program as you would with any other header file. // -// The implementation of this library will try #include-ing necessary headers for each backend. If you do not have +// The implementation of this library will try #include-ing necessary headers for some backends. If you do not have // the development packages for any particular backend you can disable it by #define-ing the appropriate MAL_NO_* // option before the implementation. // +// Note that GCC and Clang requires "-msse2", "-mavx", etc. for SIMD optimizations. // -// Building (Windows) +// +// Building for Windows +// -------------------- +// The Windows build should compile clean on all popular compilers without the need to configure any include paths +// nor link to any libraries. +// +// Building for Linux // ------------------ -// The Windows build should compile clean on all modern versions of MSVC without the need to configure any include -// paths nor link to any libraries. The same applies to MinGW/GCC and Clang. +// The Linux build only requires linking to -ldl, -lpthread and -lm. You do not need any development packages for any +// of the supported backends. // -// Building (Linux) +// Building for BSD // ---------------- -// The Linux build uses ALSA for it's backend so you will need to install the relevant ALSA development packages -// for your preferred distro. It also uses pthreads. Dependencies are dynamically linked at runtime so you do not -// need to link to -lasound nor -lpthread. You will need to link to -ldl. +// The BSD build uses OSS. Requires linking to -lpthread. Also requires linking to -lossaudio on {Open,Net}BSD, but +// not FreeBSD. // -// Building (BSD) -// -------------- -// BSD build uses OSS. Requires linking to -lossaudio on {Open,Net}BSD, but not FreeBSD. +// Building for Android +// -------------------- +// The Android build uses OpenSL|ES, and will require an appropriate API level that supports OpenSL|ES. mini_al has +// been tested against API levels 16 and 21. // -// Building (Emscripten) -// --------------------- +// Building for Emscripten +// ----------------------- // The Emscripten build currently uses SDL 1.2 for it's backend which means specifying "-s USE_SDL=2" is unecessary -// as of this version. However, if in the future there is legitimate benefit or enough demand for SDL 2 to be used -// instead, you will need to specify this when compiling. +// as of this version. // // // Playback Example @@ -77,21 +85,15 @@ // // then return the number of frames you wrote. // // // // The user data (pDevice->pUserData) is set by mal_device_init(). -// return (mal_uint32)drwav_read_f32((drwav*)pDevice->pUserData, frameCount * pDevice->channels, (float*)pSamples) / pDevice->channels; +// return (mal_uint32)mal_decoder_read((mal_decoder*)pDevice->pUserData, frameCount, pSamples); // } // // ... // -// mal_context context; -// if (mal_context_init(NULL, 0, NULL, &context) != MAL_SUCCESS) { -// printf("Failed to initialize context."); -// return -3; -// } -// -// mal_device_config config = mal_device_config_init_playback(mal_format_s16, wav.channels, wav.sampleRate, on_send_frames_to_device); +// mal_device_config config = mal_device_config_init_playback(decoder.outputFormat, decoder.outputChannels, decoder.outputSampleRate, on_send_frames_to_device); // // mal_device device; -// mal_result result = mal_device_init(&context, mal_device_type_playback, NULL, &config, pMyData, &device); +// mal_result result = mal_device_init(NULL, mal_device_type_playback, NULL, &config, &decoder /*pUserData*/, &device); // if (result != MAL_SUCCESS) { // return -1; // } @@ -118,11 +120,19 @@ // // BACKEND NUANCES // =============== -// - The absolute best latency I am able to get on DirectSound is about 10 milliseconds. This seems very -// consistent so I'm suspecting there's some kind of hard coded limit there or something. +// +// DirectSound +// ----------- // - DirectSound currently supports a maximum of 4 periods. +// +// Android +// ------- // - To capture audio on Android, remember to add the RECORD_AUDIO permission to your manifest: // +// - Only a single mal_context can be active at any given time. This is due to a limitation with OpenSL|ES. +// +// UWP +// --- // - UWP is only supported when compiling as C++. // - UWP only supports default playback and capture devices. // - UWP requires the Microphone capability to be enabled in the application's manifest (Package.appxmanifest): @@ -133,6 +143,14 @@ // // // +// PulseAudio +// ---------- +// - Each device has it's own dedicated main loop. +// +// JACK +// ---- +// - It's possible for mal_device.bufferSizeInFrames to change during run time. +// // // OPTIONS // ======= @@ -150,6 +168,12 @@ // #define MAL_NO_ALSA // Disables the ALSA backend. // +// #define MAL_NO_PULSEAUDIO +// Disables the PulseAudio backend. +// +// #define MAL_NO_JACK +// Disables the JACK backend. +// // #define MAL_NO_OSS // Disables the OSS backend. // @@ -172,6 +196,24 @@ // // #define MAL_DEFAULT_PERIODS // When a period count of 0 is specified when a device is initialized, it will default to this. +// +// #define MAL_NO_DECODING +// Disables the decoding APIs. +// +// #define MAL_NO_STDIO +// Disables file IO APIs. +// +// #define MAL_NO_SSE2 +// Disables SSE2 optimizations. +// +// #define MAL_NO_AVX +// Disables AVX optimizations. +// +// #define MAL_NO_AVX512 +// Disables AVX-512 optimizations. +// +// #define MAL_NO_NEON +// Disables NEON optimizations. #ifndef mini_al_h #define mini_al_h @@ -183,6 +225,7 @@ extern "C" { #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable:4201) // nonstandard extension used: nameless struct/union + #pragma warning(disable:4324) // structure was padded due to alignment specifier #endif // Platform/backend detection. @@ -216,15 +259,7 @@ extern "C" { #if defined(MAL_WIN32_DESKTOP) // DirectSound and WinMM backends are only supported on desktop's. #define MAL_SUPPORT_DSOUND #define MAL_SUPPORT_WINMM - #endif - - // Don't support WASAPI on older versions of MSVC for now. - #if defined(_MSC_VER) - #if _MSC_VER < 1600 - #if !defined(__audioclient_h__) - #undef MAL_SUPPORT_WASAPI - #endif - #endif + #define MAL_SUPPORT_JACK // JACK is technically supported on Windows, but I don't know how many people use it in practice... #endif #endif #if defined(MAL_UNIX) @@ -233,6 +268,10 @@ extern "C" { #define MAL_SUPPORT_ALSA #endif #endif + #if !defined(MAL_ANDROID) && !defined(MAL_EMSCRIPTEN) + #define MAL_SUPPORT_PULSEAUDIO + #define MAL_SUPPORT_JACK + #endif #if defined(MAL_APPLE) #define MAL_SUPPORT_COREAUDIO #endif @@ -265,6 +304,12 @@ extern "C" { #if !defined(MAL_NO_ALSA) && defined(MAL_SUPPORT_ALSA) #define MAL_ENABLE_ALSA #endif +#if !defined(MAL_NO_PULSEAUDIO) && defined(MAL_SUPPORT_PULSEAUDIO) + #define MAL_ENABLE_PULSEAUDIO +#endif +#if !defined(MAL_NO_JACK) && defined(MAL_SUPPORT_JACK) + #define MAL_ENABLE_JACK +#endif #if !defined(MAL_NO_COREAUDIO) && defined(MAL_SUPPORT_COREAUDIO) #define MAL_ENABLE_COREAUDIO #endif @@ -284,31 +329,65 @@ extern "C" { #define MAL_ENABLE_NULL #endif +#include // For size_t. -#if defined(_MSC_VER) && _MSC_VER < 1600 -typedef signed char mal_int8; -typedef unsigned char mal_uint8; -typedef signed short mal_int16; -typedef unsigned short mal_uint16; -typedef signed int mal_int32; -typedef unsigned int mal_uint32; -typedef signed __int64 mal_int64; -typedef unsigned __int64 mal_uint64; +#ifndef MAL_HAS_STDINT + #if defined(_MSC_VER) + #if _MSC_VER >= 1600 + #define MAL_HAS_STDINT + #endif + #else + #if defined(__has_include) && __has_include() + #define MAL_HAS_STDINT + #endif + #endif +#endif + +#ifndef MAL_HAS_STDINT +typedef signed char mal_int8; +typedef unsigned char mal_uint8; +typedef signed short mal_int16; +typedef unsigned short mal_uint16; +typedef signed int mal_int32; +typedef unsigned int mal_uint32; + #if defined(_MSC_VER) + typedef signed __int64 mal_int64; + typedef unsigned __int64 mal_uint64; + #elif defined(__GNUC__) + typedef signed long long int mal_int64; + typedef unsigned long long int mal_uint64; + #endif + #if defined(_WIN32) + #if defined(_WIN64) + typedef mal_uint64 mal_uintptr; + #else + typedef mal_uint32 mal_uintptr; + #endif + #elif defined(__GNUC__) + #if defined(__LP64__) + typedef mal_uint64 mal_uintptr; + #else + typedef mal_uint32 mal_uintptr; + #endif + #else + typedef mal_uint64 mal_uintptr; // Fallback. + #endif #else #include -typedef int8_t mal_int8; -typedef uint8_t mal_uint8; -typedef int16_t mal_int16; -typedef uint16_t mal_uint16; -typedef int32_t mal_int32; -typedef uint32_t mal_uint32; -typedef int64_t mal_int64; -typedef uint64_t mal_uint64; +typedef int8_t mal_int8; +typedef uint8_t mal_uint8; +typedef int16_t mal_int16; +typedef uint16_t mal_uint16; +typedef int32_t mal_int32; +typedef uint32_t mal_uint32; +typedef int64_t mal_int64; +typedef uint64_t mal_uint64; +typedef uintptr_t mal_uintptr; #endif -typedef mal_uint8 mal_bool8; -typedef mal_uint32 mal_bool32; -#define MAL_TRUE 1 -#define MAL_FALSE 0 +typedef mal_uint8 mal_bool8; +typedef mal_uint32 mal_bool32; +#define MAL_TRUE 1 +#define MAL_FALSE 0 typedef void* mal_handle; typedef void* mal_ptr; @@ -317,6 +396,53 @@ typedef void (* mal_proc)(); typedef struct mal_context mal_context; typedef struct mal_device mal_device; +#if defined(_MSC_VER) && !defined(_WCHAR_T_DEFINED) +typedef mal_uint16 wchar_t; +#endif + +// Define NULL for some compilers. +#ifndef NULL +#define NULL 0 +#endif + +#ifdef _MSC_VER +#define MAL_INLINE __forceinline +#else +#ifdef __GNUC__ +#define MAL_INLINE inline __attribute__((always_inline)) +#else +#define MAL_INLINE inline +#endif +#endif + +#ifdef _MSC_VER +#define MAL_ALIGN(alignment) __declspec(align(alignment)) +#else +#define MAL_ALIGN(alignment) __attribute__((aligned(alignment))) +#endif + +#ifdef _MSC_VER +#define MAL_ALIGNED_STRUCT(alignment) MAL_ALIGN(alignment) struct +#else +#define MAL_ALIGNED_STRUCT(alignment) struct MAL_ALIGN(alignment) +#endif + +// SIMD alignment in bytes. Currently set to 64 bytes in preparation for future AVX-512 optimizations. +#define MAL_SIMD_ALIGNMENT 64 + +// Thread priorties should be ordered such that the default priority of the worker thread is 0. +typedef enum +{ + mal_thread_priority_idle = -5, + mal_thread_priority_lowest = -4, + mal_thread_priority_low = -3, + mal_thread_priority_normal = -2, + mal_thread_priority_high = -1, + mal_thread_priority_highest = 0, + mal_thread_priority_realtime = 1, + mal_thread_priority_default = 0 +} mal_thread_priority; + typedef struct { mal_context* pContext; @@ -388,91 +514,122 @@ typedef struct }; } mal_event; -#if defined(_MSC_VER) && !defined(_WCHAR_T_DEFINED) -typedef mal_uint16 wchar_t; -#endif - -// Define NULL for some compilers. -#ifndef NULL -#define NULL 0 -#endif #define MAL_MAX_PERIODS_DSOUND 4 #define MAL_MAX_PERIODS_OPENAL 4 +// Standard sample rates. +#define MAL_SAMPLE_RATE_8000 8000 +#define MAL_SAMPLE_RATE_11025 11025 +#define MAL_SAMPLE_RATE_16000 16000 +#define MAL_SAMPLE_RATE_22050 22050 +#define MAL_SAMPLE_RATE_24000 24000 +#define MAL_SAMPLE_RATE_32000 32000 +#define MAL_SAMPLE_RATE_44100 44100 +#define MAL_SAMPLE_RATE_48000 48000 +#define MAL_SAMPLE_RATE_88200 88200 +#define MAL_SAMPLE_RATE_96000 96000 +#define MAL_SAMPLE_RATE_176400 176400 +#define MAL_SAMPLE_RATE_192000 192000 +#define MAL_SAMPLE_RATE_352800 352800 +#define MAL_SAMPLE_RATE_384000 384000 + +#define MAL_MIN_PCM_SAMPLE_SIZE_IN_BYTES 1 // For simplicity, mini_al does not support PCM samples that are not byte aligned. +#define MAL_MAX_PCM_SAMPLE_SIZE_IN_BYTES 8 +#define MAL_MIN_CHANNELS 1 +#define MAL_MAX_CHANNELS 32 +#define MAL_MIN_SAMPLE_RATE MAL_SAMPLE_RATE_8000 +#define MAL_MAX_SAMPLE_RATE MAL_SAMPLE_RATE_384000 + typedef mal_uint8 mal_channel; #define MAL_CHANNEL_NONE 0 -#define MAL_CHANNEL_FRONT_LEFT 1 -#define MAL_CHANNEL_FRONT_RIGHT 2 -#define MAL_CHANNEL_FRONT_CENTER 3 -#define MAL_CHANNEL_LFE 4 -#define MAL_CHANNEL_BACK_LEFT 5 -#define MAL_CHANNEL_BACK_RIGHT 6 -#define MAL_CHANNEL_FRONT_LEFT_CENTER 7 -#define MAL_CHANNEL_FRONT_RIGHT_CENTER 8 -#define MAL_CHANNEL_BACK_CENTER 9 -#define MAL_CHANNEL_SIDE_LEFT 10 -#define MAL_CHANNEL_SIDE_RIGHT 11 -#define MAL_CHANNEL_TOP_CENTER 12 -#define MAL_CHANNEL_TOP_FRONT_LEFT 13 -#define MAL_CHANNEL_TOP_FRONT_CENTER 14 -#define MAL_CHANNEL_TOP_FRONT_RIGHT 15 -#define MAL_CHANNEL_TOP_BACK_LEFT 16 -#define MAL_CHANNEL_TOP_BACK_CENTER 17 -#define MAL_CHANNEL_TOP_BACK_RIGHT 18 -#define MAL_CHANNEL_MONO MAL_CHANNEL_FRONT_CENTER -#define MAL_MAX_CHANNELS 18 - -#define MAL_MAX_SAMPLE_SIZE_IN_BYTES 8 +#define MAL_CHANNEL_MONO 1 +#define MAL_CHANNEL_FRONT_LEFT 2 +#define MAL_CHANNEL_FRONT_RIGHT 3 +#define MAL_CHANNEL_FRONT_CENTER 4 +#define MAL_CHANNEL_LFE 5 +#define MAL_CHANNEL_BACK_LEFT 6 +#define MAL_CHANNEL_BACK_RIGHT 7 +#define MAL_CHANNEL_FRONT_LEFT_CENTER 8 +#define MAL_CHANNEL_FRONT_RIGHT_CENTER 9 +#define MAL_CHANNEL_BACK_CENTER 10 +#define MAL_CHANNEL_SIDE_LEFT 11 +#define MAL_CHANNEL_SIDE_RIGHT 12 +#define MAL_CHANNEL_TOP_CENTER 13 +#define MAL_CHANNEL_TOP_FRONT_LEFT 14 +#define MAL_CHANNEL_TOP_FRONT_CENTER 15 +#define MAL_CHANNEL_TOP_FRONT_RIGHT 16 +#define MAL_CHANNEL_TOP_BACK_LEFT 17 +#define MAL_CHANNEL_TOP_BACK_CENTER 18 +#define MAL_CHANNEL_TOP_BACK_RIGHT 19 +#define MAL_CHANNEL_AUX_0 20 +#define MAL_CHANNEL_AUX_1 21 +#define MAL_CHANNEL_AUX_2 22 +#define MAL_CHANNEL_AUX_3 23 +#define MAL_CHANNEL_AUX_4 24 +#define MAL_CHANNEL_AUX_5 25 +#define MAL_CHANNEL_AUX_6 26 +#define MAL_CHANNEL_AUX_7 27 +#define MAL_CHANNEL_AUX_8 28 +#define MAL_CHANNEL_AUX_9 29 +#define MAL_CHANNEL_AUX_10 30 +#define MAL_CHANNEL_AUX_11 31 +#define MAL_CHANNEL_AUX_12 32 +#define MAL_CHANNEL_AUX_13 33 +#define MAL_CHANNEL_AUX_14 34 +#define MAL_CHANNEL_AUX_15 35 +#define MAL_CHANNEL_AUX_16 36 +#define MAL_CHANNEL_AUX_17 37 +#define MAL_CHANNEL_AUX_18 38 +#define MAL_CHANNEL_AUX_19 39 +#define MAL_CHANNEL_AUX_20 40 +#define MAL_CHANNEL_AUX_21 41 +#define MAL_CHANNEL_AUX_22 42 +#define MAL_CHANNEL_AUX_23 43 +#define MAL_CHANNEL_AUX_24 44 +#define MAL_CHANNEL_AUX_25 45 +#define MAL_CHANNEL_AUX_26 46 +#define MAL_CHANNEL_AUX_27 47 +#define MAL_CHANNEL_AUX_28 48 +#define MAL_CHANNEL_AUX_29 49 +#define MAL_CHANNEL_AUX_30 50 +#define MAL_CHANNEL_AUX_31 51 +#define MAL_CHANNEL_LEFT MAL_CHANNEL_FRONT_LEFT +#define MAL_CHANNEL_RIGHT MAL_CHANNEL_FRONT_RIGHT +#define MAL_CHANNEL_POSITION_COUNT MAL_CHANNEL_AUX_31 + 1 typedef int mal_result; #define MAL_SUCCESS 0 #define MAL_ERROR -1 // A generic error. #define MAL_INVALID_ARGS -2 -#define MAL_OUT_OF_MEMORY -3 -#define MAL_FORMAT_NOT_SUPPORTED -4 -#define MAL_NO_BACKEND -5 -#define MAL_NO_DEVICE -6 -#define MAL_API_NOT_FOUND -7 -#define MAL_DEVICE_BUSY -8 -#define MAL_DEVICE_NOT_INITIALIZED -9 -#define MAL_DEVICE_ALREADY_STARTED -10 -#define MAL_DEVICE_ALREADY_STARTING -11 -#define MAL_DEVICE_ALREADY_STOPPED -12 -#define MAL_DEVICE_ALREADY_STOPPING -13 -#define MAL_FAILED_TO_MAP_DEVICE_BUFFER -14 -#define MAL_FAILED_TO_INIT_BACKEND -15 -#define MAL_FAILED_TO_READ_DATA_FROM_CLIENT -16 -#define MAL_FAILED_TO_READ_DATA_FROM_DEVICE -17 -#define MAL_FAILED_TO_SEND_DATA_TO_CLIENT -18 -#define MAL_FAILED_TO_SEND_DATA_TO_DEVICE -19 -#define MAL_FAILED_TO_OPEN_BACKEND_DEVICE -20 -#define MAL_FAILED_TO_START_BACKEND_DEVICE -21 -#define MAL_FAILED_TO_STOP_BACKEND_DEVICE -22 -#define MAL_FAILED_TO_CREATE_MUTEX -23 -#define MAL_FAILED_TO_CREATE_EVENT -24 -#define MAL_FAILED_TO_CREATE_THREAD -25 -#define MAL_INVALID_DEVICE_CONFIG -26 -#define MAL_ACCESS_DENIED -27 -#define MAL_DSOUND_FAILED_TO_CREATE_DEVICE -1024 -#define MAL_DSOUND_FAILED_TO_SET_COOP_LEVEL -1025 -#define MAL_DSOUND_FAILED_TO_CREATE_BUFFER -1026 -#define MAL_DSOUND_FAILED_TO_QUERY_INTERFACE -1027 -#define MAL_DSOUND_FAILED_TO_SET_NOTIFICATIONS -1028 -#define MAL_ALSA_FAILED_TO_OPEN_DEVICE -2048 -#define MAL_ALSA_FAILED_TO_SET_HW_PARAMS -2049 -#define MAL_ALSA_FAILED_TO_SET_SW_PARAMS -2050 -#define MAL_ALSA_FAILED_TO_PREPARE_DEVICE -2051 -#define MAL_ALSA_FAILED_TO_RECOVER_DEVICE -2052 -#define MAL_WASAPI_FAILED_TO_CREATE_DEVICE_ENUMERATOR -3072 -#define MAL_WASAPI_FAILED_TO_CREATE_DEVICE -3073 -#define MAL_WASAPI_FAILED_TO_ACTIVATE_DEVICE -3074 -#define MAL_WASAPI_FAILED_TO_INITIALIZE_DEVICE -3075 -#define MAL_WASAPI_FAILED_TO_FIND_BEST_FORMAT -3076 -#define MAL_WASAPI_FAILED_TO_GET_INTERNAL_BUFFER -3077 -#define MAL_WASAPI_FAILED_TO_RELEASE_INTERNAL_BUFFER -3078 -#define MAL_WINMM_FAILED_TO_GET_DEVICE_CAPS -4096 -#define MAL_WINMM_FAILED_TO_GET_SUPPORTED_FORMATS -4097 +#define MAL_INVALID_OPERATION -3 +#define MAL_OUT_OF_MEMORY -4 +#define MAL_FORMAT_NOT_SUPPORTED -5 +#define MAL_NO_BACKEND -6 +#define MAL_NO_DEVICE -7 +#define MAL_API_NOT_FOUND -8 +#define MAL_DEVICE_BUSY -9 +#define MAL_DEVICE_NOT_INITIALIZED -10 +#define MAL_DEVICE_ALREADY_STARTED -11 +#define MAL_DEVICE_ALREADY_STARTING -12 +#define MAL_DEVICE_ALREADY_STOPPED -13 +#define MAL_DEVICE_ALREADY_STOPPING -14 +#define MAL_FAILED_TO_MAP_DEVICE_BUFFER -15 +#define MAL_FAILED_TO_UNMAP_DEVICE_BUFFER -16 +#define MAL_FAILED_TO_INIT_BACKEND -17 +#define MAL_FAILED_TO_READ_DATA_FROM_CLIENT -18 +#define MAL_FAILED_TO_READ_DATA_FROM_DEVICE -19 +#define MAL_FAILED_TO_SEND_DATA_TO_CLIENT -20 +#define MAL_FAILED_TO_SEND_DATA_TO_DEVICE -21 +#define MAL_FAILED_TO_OPEN_BACKEND_DEVICE -22 +#define MAL_FAILED_TO_START_BACKEND_DEVICE -23 +#define MAL_FAILED_TO_STOP_BACKEND_DEVICE -24 +#define MAL_FAILED_TO_CONFIGURE_BACKEND_DEVICE -25 +#define MAL_FAILED_TO_CREATE_MUTEX -26 +#define MAL_FAILED_TO_CREATE_EVENT -27 +#define MAL_FAILED_TO_CREATE_THREAD -28 +#define MAL_INVALID_DEVICE_CONFIG -29 +#define MAL_ACCESS_DENIED -30 typedef void (* mal_log_proc) (mal_context* pContext, mal_device* pDevice, const char* message); typedef void (* mal_recv_proc)(mal_device* pDevice, mal_uint32 frameCount, const void* pSamples); @@ -486,6 +643,8 @@ typedef enum mal_backend_dsound, mal_backend_winmm, mal_backend_alsa, + mal_backend_pulseaudio, + mal_backend_jack, mal_backend_oss, mal_backend_opensl, mal_backend_openal, @@ -498,11 +657,35 @@ typedef enum mal_device_type_capture } mal_device_type; +typedef enum +{ + mal_share_mode_shared = 0, + mal_share_mode_exclusive, +} mal_share_mode; + +typedef enum +{ + mal_stream_format_pcm = 0, +} mal_stream_format; + +typedef enum +{ + mal_stream_layout_interleaved = 0, + mal_stream_layout_deinterleaved +} mal_stream_layout; + +typedef enum +{ + mal_dither_mode_none = 0, + //mal_dither_mode_rectangle, + //mal_dither_mode_triangle +} mal_dither_mode; + typedef enum { // I like to keep these explicitly defined because they're used as a key into a lookup table. When items are - // added to this, make sure there are no gaps and that they're added to the lookup table in mal_get_sample_size_in_bytes(). - mal_format_unknown = 0, // Mainly used for indicating an error. + // added to this, make sure there are no gaps and that they're added to the lookup table in mal_get_bytes_per_sample(). + mal_format_unknown = 0, // Mainly used for indicating an error, but also used as the default for the output format for decoders. mal_format_u8 = 1, mal_format_s16 = 2, // Seems to be the most widely supported format. mal_format_s24 = 3, // Tightly packed. 3 bytes per sample. @@ -512,10 +695,21 @@ typedef enum typedef enum { - mal_channel_mix_mode_basic, // Drop excess channels; zeroed out extra channels. - mal_channel_mix_mode_blend, // Blend channels based on locality. + mal_channel_mix_mode_planar_blend = 0, // Simple averaging based on the plane(s) the channel is sitting on. + mal_channel_mix_mode_simple, // Drop excess channels; zeroed out extra channels. + mal_channel_mix_mode_default = mal_channel_mix_mode_planar_blend } mal_channel_mix_mode; +typedef enum +{ + mal_standard_channel_map_microsoft, + mal_standard_channel_map_alsa, + mal_standard_channel_map_rfc3551, // Based off AIFF. + mal_standard_channel_map_flac, + mal_standard_channel_map_vorbis, + mal_standard_channel_map_default = mal_standard_channel_map_microsoft +} mal_standard_channel_map; + typedef union { #ifdef MAL_SUPPORT_WASAPI @@ -530,6 +724,12 @@ typedef union #ifdef MAL_SUPPORT_ALSA char alsa[256]; // ALSA uses a name string for identification. #endif +#ifdef MAL_SUPPORT_PULSEAUDIO + char pulse[256]; // PulseAudio uses a name string for identification. +#endif +#ifdef MAL_SUPPORT_JACK + int jack; // JACK always uses default devices. +#endif #ifdef MAL_SUPPORT_COREAUDIO // TODO: Implement me. #endif @@ -546,14 +746,18 @@ typedef union int sdl; // SDL devices are identified with an index. #endif #ifdef MAL_SUPPORT_NULL - int nullbackend; // Always 0. + int nullbackend; // The null backend uses an integer for device IDs. #endif } mal_device_id; typedef struct { + // Basic info. This is the only information guaranteed to be filled in during device enumeration. mal_device_id id; char name[256]; + + // Detailed info. As much of this is filled as possible with mal_context_get_device_info(). + // TODO: Implement me. } mal_device_info; typedef struct @@ -562,50 +766,99 @@ typedef struct } mal_timer; + +typedef struct mal_format_converter mal_format_converter; +typedef mal_uint32 (* mal_format_converter_read_proc) (mal_format_converter* pConverter, mal_uint32 frameCount, void* pFramesOut, void* pUserData); +typedef mal_uint32 (* mal_format_converter_read_deinterleaved_proc)(mal_format_converter* pConverter, mal_uint32 frameCount, void** ppSamplesOut, void* pUserData); + +typedef struct +{ + mal_format formatIn; + mal_format formatOut; + mal_uint32 channels; + mal_stream_format streamFormatIn; + mal_stream_format streamFormatOut; + mal_dither_mode ditherMode; + mal_format_converter_read_proc onRead; + mal_format_converter_read_deinterleaved_proc onReadDeinterleaved; + void* pUserData; +} mal_format_converter_config; + +struct mal_format_converter +{ + mal_format_converter_config config; + void (* onConvertPCM)(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode); + void (* onInterleavePCM)(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels); + void (* onDeinterleavePCM)(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels); +}; + + + +typedef struct mal_channel_router mal_channel_router; +typedef mal_uint32 (* mal_channel_router_read_deinterleaved_proc)(mal_channel_router* pRouter, mal_uint32 frameCount, void** ppSamplesOut, void* pUserData); + +typedef struct +{ + mal_uint32 channelsIn; + mal_uint32 channelsOut; + mal_channel channelMapIn[MAL_MAX_CHANNELS]; + mal_channel channelMapOut[MAL_MAX_CHANNELS]; + mal_channel_mix_mode mixingMode; + mal_bool32 noSSE2 : 1; + mal_bool32 noAVX : 1; + mal_bool32 noAVX512 : 1; + mal_bool32 noNEON : 1; + mal_channel_router_read_deinterleaved_proc onReadDeinterleaved; + void* pUserData; +} mal_channel_router_config; + +struct mal_channel_router +{ + mal_channel_router_config config; + mal_bool32 isPassthrough : 1; + mal_bool32 isSimpleShuffle : 1; + mal_bool32 useSSE2 : 1; + mal_bool32 useAVX : 1; + mal_bool32 useAVX512 : 1; + mal_bool32 useNEON : 1; + mal_uint8 shuffleTable[MAL_MAX_CHANNELS]; + float weights[MAL_MAX_CHANNELS][MAL_MAX_CHANNELS]; +}; + + + typedef struct mal_src mal_src; typedef mal_uint32 (* mal_src_read_proc)(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, void* pUserData); // Returns the number of frames that were read. +typedef mal_uint32 (* mal_src_read_deinterleaved_proc)(mal_src* pSRC, mal_uint32 frameCount, void** ppSamplesOut, void* pUserData); // Returns the number of frames that were read. typedef enum { + mal_src_algorithm_linear = 0, mal_src_algorithm_none, - mal_src_algorithm_linear + mal_src_algorithm_default = mal_src_algorithm_linear } mal_src_algorithm; -#define MAL_SRC_CACHE_SIZE_IN_FRAMES 512 -typedef struct -{ - mal_src* pSRC; - float pCachedFrames[MAL_MAX_CHANNELS * MAL_SRC_CACHE_SIZE_IN_FRAMES]; - mal_uint32 cachedFrameCount; - mal_uint32 iNextFrame; -} mal_src_cache; - typedef struct { mal_uint32 sampleRateIn; mal_uint32 sampleRateOut; - mal_format formatIn; - mal_format formatOut; mal_uint32 channels; mal_src_algorithm algorithm; - mal_uint32 cacheSizeInFrames; // The number of frames to read from the client at a time. + mal_src_read_deinterleaved_proc onReadDeinterleaved; + void* pUserData; } mal_src_config; -struct mal_src +MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_src { + MAL_ALIGN(MAL_SIMD_ALIGNMENT) float samplesFromClient[MAL_MAX_CHANNELS][256]; mal_src_config config; - mal_src_read_proc onRead; - void* pUserData; - float bin[256]; - mal_src_cache cache; // <-- For simplifying and optimizing client -> memory reading. union { struct { - float alpha; - mal_bool32 isPrevFramesLoaded : 1; - mal_bool32 isNextFramesLoaded : 1; + float t; + mal_uint32 leftoverFrames; } linear; }; }; @@ -615,28 +868,36 @@ typedef mal_uint32 (* mal_dsp_read_proc)(mal_dsp* pDSP, mal_uint32 frameCount, v typedef struct { - mal_format formatIn; - mal_uint32 channelsIn; - mal_uint32 sampleRateIn; + mal_format formatIn; + mal_uint32 channelsIn; + mal_uint32 sampleRateIn; mal_channel channelMapIn[MAL_MAX_CHANNELS]; - mal_format formatOut; - mal_uint32 channelsOut; - mal_uint32 sampleRateOut; + mal_format formatOut; + mal_uint32 channelsOut; + mal_uint32 sampleRateOut; mal_channel channelMapOut[MAL_MAX_CHANNELS]; - mal_uint32 cacheSizeInFrames; // Applications should set this to 0 for now. + mal_channel_mix_mode channelMixMode; + mal_src_algorithm srcAlgorithm; + mal_bool32 allowDynamicSampleRate; + mal_dsp_read_proc onRead; + void* pUserData; } mal_dsp_config; -struct mal_dsp +MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_dsp { - mal_dsp_config config; mal_dsp_read_proc onRead; - void* pUserDataForOnRead; - mal_src src; // For sample rate conversion. - mal_channel channelMapInPostMix[MAL_MAX_CHANNELS]; // <-- When mixing, new channels may need to be created. This represents the channel map after mixing. - mal_channel channelShuffleTable[MAL_MAX_CHANNELS]; - mal_bool32 isChannelMappingRequired : 1; - mal_bool32 isSRCRequired : 1; - mal_bool32 isPassthrough : 1; // <-- Will be set to true when the DSP pipeline is an optimized passthrough. + void* pUserData; + mal_format_converter formatConverterIn; // For converting data to f32 in preparation for further processing. + mal_format_converter formatConverterOut; // For converting data to the requested output format. Used as the final step in the processing pipeline. + mal_channel_router channelRouter; // For channel conversion. + mal_src src; // For sample rate conversion. + mal_bool32 isDynamicSampleRateAllowed : 1; // mal_dsp_set_input_sample_rate() and mal_dsp_set_output_sample_rate() will fail if this is set to false. + mal_bool32 isPreFormatConversionRequired : 1; + mal_bool32 isPostFormatConversionRequired : 1; + mal_bool32 isChannelRoutingRequired : 1; + mal_bool32 isSRCRequired : 1; + mal_bool32 isChannelRoutingAtStart : 1; + mal_bool32 isPassthrough : 1; // <-- Will be set to true when the DSP pipeline is an optimized passthrough. }; @@ -648,7 +909,7 @@ typedef struct mal_channel channelMap[MAL_MAX_CHANNELS]; mal_uint32 bufferSizeInFrames; mal_uint32 periods; - mal_bool32 preferExclusiveMode; + mal_share_mode shareMode; mal_recv_proc onRecvCallback; mal_send_proc onSendCallback; mal_stop_proc onStopCallback; @@ -657,23 +918,53 @@ typedef struct { mal_bool32 noMMap; // Disables MMap mode. } alsa; + + struct + { + const char* pStreamName; + } pulse; } mal_device_config; typedef struct { mal_log_proc onLog; + mal_thread_priority threadPriority; struct { mal_bool32 useVerboseDeviceEnumeration; - mal_bool32 excludeNullDevice; } alsa; + + struct + { + const char* pApplicationName; + const char* pServerName; + mal_bool32 tryAutoSpawn; // Enables autospawning of the PulseAudio daemon if necessary. + } pulse; + + struct + { + const char* pClientName; + mal_bool32 tryStartServer; + } jack; } mal_context_config; +typedef mal_bool32 (* mal_enum_devices_callback_proc)(mal_context* pContext, mal_device_type type, const mal_device_info* pInfo, void* pUserData); + struct mal_context { - mal_backend backend; // DirectSound, ALSA, etc. + mal_backend backend; // DirectSound, ALSA, etc. mal_context_config config; + mal_mutex deviceEnumLock; // Used to make mal_context_get_devices() thread safe. + mal_mutex deviceInfoLock; // Used to make mal_context_get_device_info() thread safe. + mal_uint32 deviceInfoCapacity; // Total capacity of pDeviceInfos. + mal_uint32 playbackDeviceInfoCount; + mal_uint32 captureDeviceInfoCount; + mal_device_info* pDeviceInfos; // Playback devices first, then capture. + + mal_bool32 (* onDeviceIDEqual)(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1); + mal_result (* onEnumDevices )(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData); // Return false from the callback to stop enumeration. + mal_result (* onGetDeviceInfo)(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo); union { @@ -687,6 +978,10 @@ struct mal_context struct { /*HMODULE*/ mal_handle hDSoundDLL; + mal_proc DirectSoundCreate; + mal_proc DirectSoundEnumerateA; + mal_proc DirectSoundCaptureCreate; + mal_proc DirectSoundCaptureEnumerateA; } dsound; #endif #ifdef MAL_SUPPORT_WINMM @@ -762,8 +1057,79 @@ struct mal_context mal_proc snd_pcm_info; mal_proc snd_pcm_info_sizeof; mal_proc snd_pcm_info_get_name; + mal_proc snd_config_update_free_global; + + mal_mutex internalDeviceEnumLock; } alsa; #endif +#ifdef MAL_SUPPORT_PULSEAUDIO + struct + { + mal_handle pulseSO; + mal_proc pa_mainloop_new; + mal_proc pa_mainloop_free; + mal_proc pa_mainloop_get_api; + mal_proc pa_mainloop_iterate; + mal_proc pa_mainloop_wakeup; + mal_proc pa_context_new; + mal_proc pa_context_unref; + mal_proc pa_context_connect; + mal_proc pa_context_disconnect; + mal_proc pa_context_set_state_callback; + mal_proc pa_context_get_state; + mal_proc pa_context_get_sink_info_list; + mal_proc pa_context_get_source_info_list; + mal_proc pa_context_get_sink_info_by_name; + mal_proc pa_context_get_source_info_by_name; + mal_proc pa_operation_unref; + mal_proc pa_operation_get_state; + mal_proc pa_channel_map_init_extend; + mal_proc pa_channel_map_valid; + mal_proc pa_channel_map_compatible; + mal_proc pa_stream_new; + mal_proc pa_stream_unref; + mal_proc pa_stream_connect_playback; + mal_proc pa_stream_connect_record; + mal_proc pa_stream_disconnect; + mal_proc pa_stream_get_state; + 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_get_device_name; + mal_proc pa_stream_set_write_callback; + mal_proc pa_stream_set_read_callback; + mal_proc pa_stream_flush; + mal_proc pa_stream_drain; + mal_proc pa_stream_cork; + mal_proc pa_stream_trigger; + mal_proc pa_stream_begin_write; + mal_proc pa_stream_write; + mal_proc pa_stream_peek; + mal_proc pa_stream_drop; + } pulse; +#endif +#ifdef MAL_SUPPORT_JACK + struct + { + mal_handle jackSO; + mal_proc jack_client_open; + mal_proc jack_client_close; + mal_proc jack_client_name_size; + mal_proc jack_set_process_callback; + mal_proc jack_set_buffer_size_callback; + mal_proc jack_on_shutdown; + mal_proc jack_get_sample_rate; + mal_proc jack_get_buffer_size; + mal_proc jack_get_ports; + mal_proc jack_activate; + mal_proc jack_deactivate; + mal_proc jack_connect; + mal_proc jack_port_register; + mal_proc jack_port_name; + mal_proc jack_port_get_buffer; + mal_proc jack_free; + } jack; +#endif #ifdef MAL_SUPPORT_COREAUDIO struct { @@ -807,7 +1173,6 @@ struct mal_context mal_proc alcCaptureStart; mal_proc alcCaptureStop; mal_proc alcCaptureSamples; - mal_proc alEnable; mal_proc alDisable; mal_proc alIsEnabled; @@ -867,8 +1232,8 @@ struct mal_context mal_proc alGetBufferiv; mal_bool32 isEnumerationSupported : 1; - mal_bool32 isFloat32Supported : 1; - mal_bool32 isMCFormatsSupported : 1; + mal_bool32 isFloat32Supported : 1; + mal_bool32 isMCFormatsSupported : 1; } openal; #endif #ifdef MAL_SUPPORT_SDL @@ -908,6 +1273,7 @@ struct mal_context mal_proc CoCreateInstance; mal_proc CoTaskMemFree; mal_proc PropVariantClear; + mal_proc StringFromGUID2; /*HMODULE*/ mal_handle hUser32DLL; mal_proc GetForegroundWindow; @@ -928,20 +1294,25 @@ struct mal_context mal_proc pthread_cond_destroy; mal_proc pthread_cond_wait; mal_proc pthread_cond_signal; + mal_proc pthread_attr_init; + mal_proc pthread_attr_destroy; + mal_proc pthread_attr_setschedpolicy; + mal_proc pthread_attr_getschedparam; + mal_proc pthread_attr_setschedparam; } posix; #endif int _unused; }; }; -struct mal_device +MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device { mal_context* pContext; mal_device_type type; mal_format format; mal_uint32 channels; mal_uint32 sampleRate; - mal_uint8 channelMap[MAL_MAX_CHANNELS]; + mal_channel channelMap[MAL_MAX_CHANNELS]; mal_uint32 bufferSizeInFrames; mal_uint32 periods; mal_uint32 state; @@ -956,13 +1327,18 @@ struct mal_device mal_event stopEvent; mal_thread thread; mal_result workResult; // This is set by the worker thread after it's finished doing a job. + mal_bool32 usingDefaultFormat : 1; + mal_bool32 usingDefaultChannels : 1; + mal_bool32 usingDefaultSampleRate : 1; + mal_bool32 usingDefaultChannelMap : 1; mal_bool32 usingDefaultBufferSize : 1; mal_bool32 usingDefaultPeriods : 1; mal_bool32 exclusiveMode : 1; + mal_bool32 isOwnerOfContext : 1; // When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into mal_device_init(). mal_format internalFormat; mal_uint32 internalChannels; mal_uint32 internalSampleRate; - mal_uint8 internalChannelMap[MAL_MAX_CHANNELS]; + mal_channel internalChannelMap[MAL_MAX_CHANNELS]; mal_dsp dsp; // Samples run through this to convert samples to a format suitable for use by the backend. mal_uint32 _dspFrameCount; // Internal use only. Used when running the device -> DSP -> client pipeline. See mal_device__on_read_from_device(). const mal_uint8* _dspFrames; // ^^^ AS ABOVE ^^^ @@ -983,7 +1359,6 @@ struct mal_device #ifdef MAL_SUPPORT_DSOUND struct { - /*HMODULE*/ mal_handle hDSoundDLL; /*LPDIRECTSOUND*/ mal_ptr pPlayback; /*LPDIRECTSOUNDBUFFER*/ mal_ptr pPlaybackPrimaryBuffer; /*LPDIRECTSOUNDBUFFER*/ mal_ptr pPlaybackBuffer; @@ -1019,6 +1394,26 @@ struct mal_device void* pIntermediaryBuffer; } alsa; #endif +#ifdef MAL_SUPPORT_PULSEAUDIO + struct + { + /*pa_mainloop**/ mal_ptr pMainLoop; + /*pa_mainloop_api**/ mal_ptr pAPI; + /*pa_context**/ mal_ptr pPulseContext; + /*pa_stream**/ mal_ptr pStream; + /*pa_context_state*/ mal_uint32 pulseContextState; + mal_uint32 fragmentSizeInBytes; + mal_bool32 breakFromMainLoop : 1; + } pulse; +#endif +#ifdef MAL_SUPPORT_JACK + struct + { + /*jack_client_t**/ mal_ptr pClient; + /*jack_port_t**/ mal_ptr pPorts[MAL_MAX_CHANNELS]; + float* pIntermediaryBuffer; // Typed as a float because JACK is always floating point. + } jack; +#endif #ifdef MAL_SUPPORT_COREAUDIO struct { @@ -1088,7 +1483,7 @@ struct mal_device // // The context is used for selecting and initializing the relevant backends. // -// Note that the location of the device cannot change throughout it's lifetime. Consider allocating +// Note that the location of the context cannot change throughout it's lifetime. Consider allocating // the mal_context object with malloc() if this is an issue. The reason for this is that a pointer // to the context is stored in the mal_device structure. // @@ -1097,24 +1492,26 @@ struct mal_device // - WASAPI // - DirectSound // - WinMM -// - ALSA // - OSS +// - PulseAudio +// - ALSA +// - JACK // - OpenSL|ES // - OpenAL // - SDL // - Null // -// The onLog callback is used for posting log messages back to the client for diagnostics, debugging, -// etc. You can pass NULL for this if you do not need it. +// is used to configure the context. Use the onLog config to set a callback for whenever a +// log message is posted. The priority of the worker thread can be set with the threadPriority config. +// +// It is recommended that only a single context is active at any given time because it's a bulky data +// structure which performs run-time linking for the relevant backends every time it's initialized. // // Return Value: // MAL_SUCCESS if successful; any other error code otherwise. // // Thread Safety: UNSAFE -// -// Effeciency: LOW -// This will dynamically load backends DLLs/SOs (such as dsound.dll). -mal_result mal_context_init(mal_backend backends[], mal_uint32 backendCount, const mal_context_config* pConfig, mal_context* pContext); +mal_result mal_context_init(const mal_backend backends[], mal_uint32 backendCount, const mal_context_config* pConfig, mal_context* pContext); // Uninitializes a context. // @@ -1124,48 +1521,117 @@ mal_result mal_context_init(mal_backend backends[], mal_uint32 backendCount, con // MAL_SUCCESS if successful; any other error code otherwise. // // Thread Safety: UNSAFE -// -// Efficiency: LOW -// This will unload the backend DLLs/SOs. mal_result mal_context_uninit(mal_context* pContext); -// Enumerates over each device of the given type (playback or capture). +// Enumerates over every device (both playback and capture). // -// It is _not_ safe to assume the first enumerated device is the default device. +// This is a lower-level enumeration function to the easier to use mal_context_get_devices(). Use +// mal_context_enumerate_devices() if you would rather not incur an internal heap allocation, or +// it simply suits your code better. +// +// Do _not_ assume the first enumerated device of a given type is the default device. // // Some backends and platforms may only support default playback and capture devices. // +// Note that this only retrieves the ID and name/description of the device. The reason for only +// retrieving basic information is that it would otherwise require opening the backend device in +// order to probe it for more detailed information which can be inefficient. Consider using +// mal_context_get_device_info() for this, but don't call it from within the enumeration callback. +// +// In general, you should not do anything complicated from within the callback. In particular, do +// not try initializing a device from within the callback. +// +// Consider using mal_context_get_devices() for a simpler and safer API, albeit at the expense of +// an internal heap allocation. +// +// Returning false from the callback will stop enumeration. Returning true will continue enumeration. +// // Return Value: // MAL_SUCCESS if successful; any other error code otherwise. // -// Thread Safety: SAFE, SEE NOTES. -// This API uses an application-defined buffer for output. This is thread-safe so long as the -// application ensures mutal exclusion to the output buffer at their level. +// Thread Safety: SAFE +// This is guarded using a simple mutex lock. +mal_result mal_context_enumerate_devices(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData); + +// Retrieves basic information about every active playback and/or capture device. // -// Efficiency: LOW -mal_result mal_enumerate_devices(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo); +// You can pass in NULL for the playback or capture lists in which case they'll be ignored. +// +// It is _not_ safe to assume the first device in the list is the default device. +// +// The returned pointers will become invalid upon the next call this this function, or when the +// context is uninitialized. Do not free the returned pointers. +// +// This function follows the same enumeration rules as mal_context_enumerate_devices(). See +// documentation for mal_context_enumerate_devices() for more information. +// +// Return Value: +// MAL_SUCCESS if successful; any other error code otherwise. +// +// Thread Safety: SAFE +// Since each call to this function invalidates the pointers from the previous call, you +// should not be calling this simultaneously across multiple threads. Instead, you need to +// make a copy of the returned data with your own higher level synchronization. +mal_result mal_context_get_devices(mal_context* pContext, mal_device_info** ppPlaybackDeviceInfos, mal_uint32* pPlaybackDeviceCount, mal_device_info** ppCaptureDeviceInfos, mal_uint32* pCaptureDeviceCount); + +// Retrieves information about a device with the given ID. +// +// Do _not_ call this from within the mal_context_enumerate_devices() callback. +// +// It's possible for a device to have different information and capabilities depending on wether or +// not it's opened in shared or exclusive mode. For example, in shared mode, WASAPI always uses +// floating point samples for mixing, but in exclusive mode it can be anything. Therefore, this +// function allows you to specify which share mode you want information for. Note that not all +// backends and devices support shared or exclusive mode, in which case this function will fail +// if the requested share mode is unsupported. +// +// This leaves pDeviceInfo unmodified in the result of an error. +// +// Return Value: +// MAL_SUCCESS if successful; any other error code otherwise. +// +// Thread Safety: SAFE +// This is guarded using a simple mutex lock. +mal_result mal_context_get_device_info(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo); // Initializes a device. // +// The context can be null in which case it uses the default. This is equivalent to passing in a +// context that was initialized like so: +// +// mal_context_init(NULL, 0, NULL, &context); +// +// Do not pass in null for the context if you are needing to open multiple devices. You can, +// however, use null when initializing the first device, and then use device.pContext for the +// initialization of other devices. +// // The device ID (pDeviceID) can be null, in which case the default device is used. Otherwise, you -// can retrieve the ID by calling mal_enumerate_devices() and using the ID from the returned data. +// can retrieve the ID by calling mal_context_get_devices() and using the ID from the returned data. // Set pDeviceID to NULL to use the default device. Do _not_ rely on the first device ID returned -// by mal_enumerate_devices() to be the default device. +// by mal_context_enumerate_devices() or mal_context_get_devices() to be the default device. // -// This will try it's hardest to create a valid device, even if it means adjusting input arguments. -// Look at pDevice->internalChannels, pDevice->internalSampleRate, etc. to determine the actual -// properties after initialization. +// The device's configuration is controlled with pConfig. This allows you to configure the sample +// format, channel count, sample rate, etc. Before calling mal_device_init(), you will most likely +// want to initialize a mal_device_config object using mal_device_config_init(), +// mal_device_config_init_playback(), etc. You can also pass in NULL for the device config in +// which case it will use defaults, but will require you to call mal_device_set_recv_callback() or +// mal_device_set_send_callback() before starting the device. // -// If is 0, it will default to MAL_DEFAULT_BUFFER_SIZE_IN_MILLISECONDS. If -// is set to 0 it will default to MAL_DEFAULT_PERIODS. +// Passing in 0 to any property in pConfig will force the use of a default value. In the case of +// sample format, channel count, sample rate and channel map it will default to the values used by +// the backend's internal device. If is 0, it will default to +// MAL_DEFAULT_BUFFER_SIZE_IN_MILLISECONDS. If is set to 0 it will default to +// MAL_DEFAULT_PERIODS. +// +// When sending or receiving data to/from a device, mini_al will internally perform a format +// conversion to convert between the format specified by pConfig and the format used internally by +// the backend. If you pass in NULL for pConfig or 0 for the sample format, channel count, +// sample rate _and_ channel map, data transmission will run on an optimized pass-through fast path. // // The property controls how frequently the background thread is woken to check for more // data. It's tied to the buffer size, so as an example, if your buffer size is equivalent to 10 // milliseconds and you have 2 periods, the CPU will wake up approximately every 5 milliseconds. // -// Use mal_device_config_init(), mal_device_config_init_playback(), etc. to initialize a -// mal_device_config object. -// // When compiling for UWP you must ensure you call this function on the main UI thread because the // operating system may need to present the user with a message asking for permissions. Please refer // to the official documentation for ActivateAudioInterfaceAsync() for more information. @@ -1175,15 +1641,16 @@ mal_result mal_enumerate_devices(mal_context* pContext, mal_device_type type, ma // // Thread Safety: UNSAFE // It is not safe to call this function simultaneously for different devices because some backends -// depend on and mutate global state (such as OpenSL|ES). The same applies to calling this as the +// depend on and mutate global state (such as OpenSL|ES). The same applies to calling this at the // same time as mal_device_uninit(). -// -// Results are undefined if you try using a device before this function has returned. -// -// Efficiency: LOW -// This is just slow due to the nature of it being an initialization API. mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, void* pUserData, mal_device* pDevice); +// Initializes a device without a context, with extra parameters for controlling the configuration +// of the internal self-managed context. +// +// See mal_device_init() and mal_context_init(). +mal_result mal_device_init_ex(const mal_backend backends[], mal_uint32 backendCount, const mal_context_config* pContextConfig, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, void* pUserData, mal_device* pDevice); + // Uninitializes a device. // // This will explicitly stop the device. You do not need to call mal_device_stop() beforehand, but it's @@ -1195,19 +1662,12 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi // Thread Safety: UNSAFE // As soon as this API is called the device should be considered undefined. All bets are off if you // try using the device at the same time as uninitializing it. -// -// Efficiency: LOW -// This will stop the device with mal_device_stop() which is a slow, synchronized call. It also needs -// to destroy internal objects like the backend-specific objects and the background thread. void mal_device_uninit(mal_device* pDevice); // Sets the callback to use when the application has received data from the device. // // Thread Safety: SAFE // This API is implemented as a simple atomic assignment. -// -// Efficiency: HIGH -// This is just an atomic assignment. void mal_device_set_recv_callback(mal_device* pDevice, mal_recv_proc proc); // Sets the callback to use when the application needs to send data to the device for playback. @@ -1218,18 +1678,12 @@ void mal_device_set_recv_callback(mal_device* pDevice, mal_recv_proc proc); // // Thread Safety: SAFE // This API is implemented as a simple atomic assignment. -// -// Efficiency: HIGH -// This is just an atomic assignment. void mal_device_set_send_callback(mal_device* pDevice, mal_send_proc proc); // Sets the callback to use when the device has stopped, either explicitly or as a result of an error. // // Thread Safety: SAFE // This API is implemented as a simple atomic assignment. -// -// Efficiency: HIGH -// This is just an atomic assignment. void mal_device_set_stop_callback(mal_device* pDevice, mal_stop_proc proc); // Activates the device. For playback devices this begins playback. For capture devices it begins @@ -1239,6 +1693,9 @@ void mal_device_set_stop_callback(mal_device* pDevice, mal_stop_proc proc); // returning. The reason for this is to ensure there is valid audio data in the buffer, which needs // to be done _before_ the device begins playback. // +// This API waits until the backend device has been started for real by the worker thread. It also +// waits on a mutex for thread-safety. +// // Return Value: // - MAL_SUCCESS if successful; any other error code otherwise. // - MAL_INVALID_ARGS @@ -1262,14 +1719,15 @@ void mal_device_set_stop_callback(mal_device* pDevice, mal_stop_proc proc); // There was a backend-specific error starting the device. // // Thread Safety: SAFE -// -// Efficiency: LOW -// This API waits until the backend device has been started for real by the worker thread. It also -// waits on a mutex for thread-safety. mal_result mal_device_start(mal_device* pDevice); // Puts the device to sleep, but does not uninitialize it. Use mal_device_start() to start it up again. // +// This API needs to wait on the worker thread to stop the backend device properly before returning. It +// also waits on a mutex for thread-safety. In addition, some backends need to wait for the device to +// finish playback/recording of the current fragment which can take some time (usually proportionate to +// the buffer size that was specified at initialization time). +// // Return Value: // - MAL_SUCCESS if successful; any other error code otherwise. // - MAL_INVALID_ARGS @@ -1289,50 +1747,54 @@ mal_result mal_device_start(mal_device* pDevice); // There was a backend-specific error stopping the device. // // Thread Safety: SAFE -// -// Efficiency: LOW -// This API needs to wait on the worker thread to stop the backend device properly before returning. It -// also waits on a mutex for thread-safety. -// -// In addition, some backends need to wait for the device to finish playback/recording of the current -// fragment which can take some time (usually proportionate to the buffer size used when initializing -// the device). mal_result mal_device_stop(mal_device* pDevice); // Determines whether or not the device is started. // +// This is implemented as a simple accessor. +// // Return Value: // True if the device is started, false otherwise. // // Thread Safety: SAFE // If another thread calls mal_device_start() or mal_device_stop() at this same time as this function // is called, there's a very small chance the return value will be out of sync. -// -// Efficiency: HIGH -// This is implemented with a simple accessor. mal_bool32 mal_device_is_started(mal_device* pDevice); // Retrieves the size of the buffer in bytes for the given device. // +// This API is efficient and is implemented with just a few 32-bit integer multiplications. +// // Thread Safety: SAFE // This is calculated from constant values which are set at initialization time and never change. -// -// Efficiency: HIGH -// This is implemented with just a few 32-bit integer multiplications. mal_uint32 mal_device_get_buffer_size_in_bytes(mal_device* pDevice); // Retrieves the size of a sample in bytes for the given format. // +// This API is efficient and is implemented using a lookup table. +// // Thread Safety: SAFE // This is API is pure. -// -// Efficiency: HIGH -// This is implemented with a lookup table. -mal_uint32 mal_get_sample_size_in_bytes(mal_format format); +mal_uint32 mal_get_bytes_per_sample(mal_format format); +static MAL_INLINE mal_uint32 mal_get_bytes_per_frame(mal_format format, mal_uint32 channels) { return mal_get_bytes_per_sample(format) * channels; } + // Helper function for initializing a mal_context_config object. mal_context_config mal_context_config_init(mal_log_proc onLog); +// Initializes a default device config. +// +// A default configuration will configure the device such that the format, channel count, sample rate and channel map are +// the same as the backend's internal configuration. This means the application loses explicit control of these properties, +// but in return gets an optimized fast path for data transmission since mini_al will be releived of all format conversion +// duties. You will not typically want to use default configurations unless you have some specific low-latency requirements. +// +// mal_device_config_init(), mal_device_config_init_playback(), etc. will allow you to explicitly set the sample format, +// channel count, etc. +mal_device_config mal_device_config_init_default(); +mal_device_config mal_device_config_init_default_capture(mal_recv_proc onRecvCallback); +mal_device_config mal_device_config_init_default_playback(mal_send_proc onSendCallback); + // Helper function for initializing a mal_device_config object. // // This is just a helper API, and as such the returned object can be safely modified as needed. @@ -1343,32 +1805,40 @@ mal_context_config mal_context_config_init(mal_log_proc onLog); // |---------------|------------------------------| // | Channel Count | Mapping | // |---------------|------------------------------| -// | 1 (Mono) | 0: MAL_CHANNEL_FRONT_CENTER | +// | 1 (Mono) | 0: MAL_CHANNEL_MONO | // |---------------|------------------------------| // | 2 (Stereo) | 0: MAL_CHANNEL_FRONT_LEFT | // | | 1: MAL_CHANNEL_FRONT_RIGHT | // |---------------|------------------------------| -// | 3 (2.1) | 0: MAL_CHANNEL_FRONT_LEFT | +// | 3 | 0: MAL_CHANNEL_FRONT_LEFT | // | | 1: MAL_CHANNEL_FRONT_RIGHT | -// | | 2: MAL_CHANNEL_LFE | +// | | 2: MAL_CHANNEL_FRONT_CENTER | // |---------------|------------------------------| -// | 4 (Quad) | 0: MAL_CHANNEL_FRONT_LEFT | +// | 4 (Surround) | 0: MAL_CHANNEL_FRONT_LEFT | // | | 1: MAL_CHANNEL_FRONT_RIGHT | -// | | 2: MAL_CHANNEL_BACK_LEFT | -// | | 3: MAL_CHANNEL_BACK_RIGHT | +// | | 2: MAL_CHANNEL_FRONT_CENTER | +// | | 3: MAL_CHANNEL_BACK_CENTER | // |---------------|------------------------------| -// | 5 (4.1) | 0: MAL_CHANNEL_FRONT_LEFT | +// | 5 | 0: MAL_CHANNEL_FRONT_LEFT | // | | 1: MAL_CHANNEL_FRONT_RIGHT | -// | | 2: MAL_CHANNEL_BACK_LEFT | -// | | 3: MAL_CHANNEL_BACK_RIGHT | -// | | 4: MAL_CHANNEL_LFE | +// | | 2: MAL_CHANNEL_FRONT_CENTER | +// | | 3: MAL_CHANNEL_BACK_LEFT | +// | | 4: MAL_CHANNEL_BACK_RIGHT | // |---------------|------------------------------| // | 6 (5.1) | 0: MAL_CHANNEL_FRONT_LEFT | // | | 1: MAL_CHANNEL_FRONT_RIGHT | // | | 2: MAL_CHANNEL_FRONT_CENTER | // | | 3: MAL_CHANNEL_LFE | -// | | 4: MAL_CHANNEL_BACK_LEFT | -// | | 5: MAL_CHANNEL_BACK_RIGHT | +// | | 4: MAL_CHANNEL_SIDE_LEFT | +// | | 5: MAL_CHANNEL_SIDE_RIGHT | +// |---------------|------------------------------| +// | 7 | 0: MAL_CHANNEL_FRONT_LEFT | +// | | 1: MAL_CHANNEL_FRONT_RIGHT | +// | | 2: MAL_CHANNEL_FRONT_CENTER | +// | | 3: MAL_CHANNEL_LFE | +// | | 4: MAL_CHANNEL_BACK_CENTER | +// | | 4: MAL_CHANNEL_SIDE_LEFT | +// | | 5: MAL_CHANNEL_SIDE_RIGHT | // |---------------|------------------------------| // | 8 (7.1) | 0: MAL_CHANNEL_FRONT_LEFT | // | | 1: MAL_CHANNEL_FRONT_RIGHT | @@ -1388,25 +1858,153 @@ mal_context_config mal_context_config_init(mal_log_proc onLog); // // Efficiency: HIGH // This just returns a stack allocated object and consists of just a few assignments. -mal_device_config mal_device_config_init(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_recv_proc onRecvCallback, mal_send_proc onSendCallback); +mal_device_config mal_device_config_init_ex(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_channel channelMap[MAL_MAX_CHANNELS], mal_recv_proc onRecvCallback, mal_send_proc onSendCallback); + +// A simplified version of mal_device_config_init_ex(). +static MAL_INLINE mal_device_config mal_device_config_init(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_recv_proc onRecvCallback, mal_send_proc onSendCallback) { return mal_device_config_init_ex(format, channels, sampleRate, NULL, onRecvCallback, onSendCallback); } // A simplified version of mal_device_config_init() for capture devices. -static inline mal_device_config mal_device_config_init_capture(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_recv_proc onRecvCallback) { return mal_device_config_init(format, channels, sampleRate, onRecvCallback, NULL); } +static MAL_INLINE mal_device_config mal_device_config_init_capture_ex(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_channel channelMap[MAL_MAX_CHANNELS], mal_recv_proc onRecvCallback) { return mal_device_config_init_ex(format, channels, sampleRate, channelMap, onRecvCallback, NULL); } +static MAL_INLINE mal_device_config mal_device_config_init_capture(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_recv_proc onRecvCallback) { return mal_device_config_init_capture_ex(format, channels, sampleRate, NULL, onRecvCallback); } // A simplified version of mal_device_config_init() for playback devices. -static inline mal_device_config mal_device_config_init_playback(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_send_proc onSendCallback) { return mal_device_config_init(format, channels, sampleRate, NULL, onSendCallback); } +static MAL_INLINE mal_device_config mal_device_config_init_playback_ex(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_channel channelMap[MAL_MAX_CHANNELS], mal_send_proc onSendCallback) { return mal_device_config_init_ex(format, channels, sampleRate, channelMap, NULL, onSendCallback); } +static MAL_INLINE mal_device_config mal_device_config_init_playback(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_send_proc onSendCallback) { return mal_device_config_init_playback_ex(format, channels, sampleRate, NULL, onSendCallback); } +// Helper for retrieving a standard channel map. +void mal_get_standard_channel_map(mal_standard_channel_map standardChannelMap, mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]); + +// Copies a channel map. +void mal_channel_map_copy(mal_channel* pOut, mal_channel* pIn, mal_uint32 channels); -/////////////////////////////////////////////////////////////////////////////// +// Determines whether or not a channel map is valid. // -// SRC +// A blank channel map is valid (all channels set to MAL_CHANNEL_NONE). The way a blank channel map is handled is context specific, but +// is usually treated as a passthrough. // -/////////////////////////////////////////////////////////////////////////////// +// Invalid channel maps: +// - A channel map with no channels +// - A channel map with more than one channel and a mono channel +mal_bool32 mal_channel_map_valid(mal_uint32 channels, const mal_channel channelMap[MAL_MAX_CHANNELS]); + +// Helper for comparing two channel maps for equality. +// +// This assumes the channel count is the same between the two. +mal_bool32 mal_channel_map_equal(mal_uint32 channels, const mal_channel channelMapA[MAL_MAX_CHANNELS], const mal_channel channelMapB[MAL_MAX_CHANNELS]); + +// Helper for determining if a channel map is blank (all channels set to MAL_CHANNEL_NONE). +mal_bool32 mal_channel_map_blank(mal_uint32 channels, const mal_channel channelMap[MAL_MAX_CHANNELS]); + +// Helper for determining whether or not a channel is present in the given channel map. +mal_bool32 mal_channel_map_contains_channel_position(mal_uint32 channels, const mal_channel channelMap[MAL_MAX_CHANNELS], mal_channel channelPosition); + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Format Conversion +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Initializes a format converter. +mal_result mal_format_converter_init(const mal_format_converter_config* pConfig, mal_format_converter* pConverter); + +// Reads data from the format converter as interleaved channels. +mal_uint64 mal_format_converter_read(mal_format_converter* pConverter, mal_uint64 frameCount, void* pFramesOut, void* pUserData); + +// Reads data from the format converter as deinterleaved channels. +mal_uint64 mal_format_converter_read_deinterleaved(mal_format_converter* pConverter, mal_uint64 frameCount, void** ppSamplesOut, void* pUserData); + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Channel Routing +// =============== +// There are two main things you can do with the channel router: +// 1) Rearrange channels +// 2) Convert from one channel count to another +// +// Channel Rearrangement +// --------------------- +// A simple example of channel rearrangement may be swapping the left and right channels in a stereo stream. To do this you just pass in the same channel +// count for both the input and output with channel maps that contain the same channels (in a different order). +// +// Channel Conversion +// ------------------ +// The channel router can also convert from one channel count to another, such as converting a 5.1 stream to stero. When changing the channel count, the +// router will first perform a 1:1 mapping of channel positions that are present in both the input and output channel maps. The second thing it will do +// is distribute the input mono channel (if any) across all output channels, excluding any None and LFE channels. If there is an output mono channel, all +// input channels will be averaged, excluding any None and LFE channels. +// +// The last case to consider is when a channel position in the input channel map is not present in the output channel map, and vice versa. In this case the +// channel router will perform a blend of other related channels to produce an audible channel. There are several blending modes. +// 1) Simple +// Unmatched channels are silenced. +// 2) Planar Blending +// Channels are blended based on a set of planes that each speaker emits audio from. +// +// Planar Blending +// --------------- +// In this mode, channel positions are associated with a set of planes where the channel conceptually emits audio from. An example is the front/left speaker. +// This speaker is positioned to the front of the listener, so you can think of it as emitting audio from the front plane. It is also positioned to the left +// of the listener so you can think of it as also emitting audio from the left plane. Now consider the (unrealistic) situation where the input channel map +// contains only the front/left channel position, but the output channel map contains both the front/left and front/center channel. When deciding on the audio +// data to send to the front/center speaker (which has no 1:1 mapping with an input channel) we need to use some logic based on our available input channel +// positions. +// +// As mentioned earlier, our front/left speaker is, conceptually speaking, emitting audio from the front _and_ the left planes. Similarly, the front/center +// speaker is emitting audio from _only_ the front plane. What these two channels have in common is that they are both emitting audio from the front plane. +// Thus, it makes sense that the front/center speaker should receive some contribution from the front/left channel. How much contribution depends on their +// planar relationship (thus the name of this blending technique). +// +// Because the front/left channel is emitting audio from two planes (front and left), you can think of it as though it's willing to dedicate 50% of it's total +// volume to each of it's planes (a channel position emitting from 1 plane would be willing to given 100% of it's total volume to that plane, and a channel +// position emitting from 3 planes would be willing to given 33% of it's total volume to each plane). Similarly, the front/center speaker is emitting audio +// from only one plane so you can think of it as though it's willing to _take_ 100% of it's volume from front plane emissions. Now, since the front/left +// channel is willing to _give_ 50% of it's total volume to the front plane, and the front/center speaker is willing to _take_ 100% of it's total volume +// from the front, you can imagine that 50% of the front/left speaker will be given to the front/center speaker. +// +// Usage +// ----- +// To use the channel router you need to specify three things: +// 1) The input channel count and channel map +// 2) The output channel count and channel map +// 3) The mixing mode to use in the case where a 1:1 mapping is unavailable +// +// Note that input and output data is always deinterleaved 32-bit floating point. +// +// Initialize the channel router with mal_channel_router_init(). You will need to pass in a config object which specifies the input and output configuration, +// mixing mode and a callback for sending data to the router. This callback will be called when input data needs to be sent to the router for processing. +// +// Read data from the channel router with mal_channel_router_read_deinterleaved(). Output data is always 32-bit floating point. +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Initializes a channel router where it is assumed that the input data is non-interleaved. +mal_result mal_channel_router_init(const mal_channel_router_config* pConfig, mal_channel_router* pRouter); + +// Reads data from the channel router as deinterleaved channels. +mal_uint64 mal_channel_router_read_deinterleaved(mal_channel_router* pRouter, mal_uint64 frameCount, void** ppSamplesOut, void* pUserData); + +// Helper for initializing a channel router config. +mal_channel_router_config mal_channel_router_config_init(mal_uint32 channelsIn, const mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_uint32 channelsOut, const mal_channel channelMapOut[MAL_MAX_CHANNELS], mal_channel_mix_mode mixingMode, mal_channel_router_read_deinterleaved_proc onRead, void* pUserData); + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Sample Rate Conversion +// ====================== +// Note that mini_al currently only supports low quality linear sample rate conversion. +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 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(const mal_src_config* pConfig, mal_src* pSRC); + +// Dynamically adjusts the input sample rate. +mal_result mal_src_set_input_sample_rate(mal_src* pSRC, mal_uint32 sampleRateIn); // Dynamically adjusts the output sample rate. // @@ -1417,43 +2015,56 @@ mal_result mal_src_set_output_sample_rate(mal_src* pSRC, mal_uint32 sampleRateOu // Reads a number of frames. // // Returns the number of frames actually read. -mal_uint32 mal_src_read_frames(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut); +mal_uint64 mal_src_read_deinterleaved(mal_src* pSRC, mal_uint64 frameCount, void** ppSamplesOut, void* pUserData); -// The same mal_src_read_frames() with extra control over whether or not the internal buffers should be flushed at the end. +// The same mal_src_read_deinterleaved() with extra control over whether or not the internal buffers should be flushed at the end. // // Internally there exists a buffer that keeps track of the previous and next samples for sample rate conversion. The simple // version of this function does _not_ flush this buffer because otherwise it causes glitches for streaming based conversion // pipelines. The problem, however, is that sometimes you need those last few samples (such as if you're doing a bulk conversion // of a static file). Enabling flushing will fix this for you. -mal_uint32 mal_src_read_frames_ex(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush); +mal_uint64 mal_src_read_deinterleaved_ex(mal_src* pSRC, mal_uint64 frameCount, void** ppSamplesOut, mal_bool32 flush, void* pUserData); -/////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // DSP // -/////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 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(const mal_dsp_config* pConfig, mal_dsp* pDSP); + +// Dynamically adjusts the input sample rate. +// +// This will fail is the DSP was not initialized with allowDynamicSampleRate. +mal_result mal_dsp_set_input_sample_rate(mal_dsp* pDSP, mal_uint32 sampleRateOut); // 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. +// +// This will fail is the DSP was not initialized with allowDynamicSampleRate. 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. // // This this _not_ flush the internal buffers which means you may end up with a few less frames than you may expect. Look at // mal_dsp_read_frames_ex() if you want to flush the buffers at the end of the read. -mal_uint32 mal_dsp_read_frames(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut); +mal_uint64 mal_dsp_read(mal_dsp* pDSP, mal_uint64 frameCount, void* pFramesOut, void* pUserData); // The same mal_dsp_read_frames() with extra control over whether or not the internal buffers should be flushed at the end. // // See documentation for mal_src_read_frames_ex() for an explanation on flushing. -mal_uint32 mal_dsp_read_frames_ex(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush); +mal_uint64 mal_dsp_read_ex(mal_dsp* pDSP, mal_uint64 frameCount, void* pFramesOut, mal_bool32 flush, void* pUserData); + +// Helper for initializing a mal_dsp_config object. +mal_dsp_config mal_dsp_config_init_new(); +mal_dsp_config mal_dsp_config_init(mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, mal_dsp_read_proc onRead, void* pUserData); +mal_dsp_config mal_dsp_config_init_ex(mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, mal_channel channelMapOut[MAL_MAX_CHANNELS], mal_dsp_read_proc onRead, void* pUserData); + // High-level helper for doing a full format conversion in one go. Returns the number of output frames. Call this with pOut set to NULL to // determine the required size of the output buffer. @@ -1461,18 +2072,16 @@ mal_uint32 mal_dsp_read_frames_ex(mal_dsp* pDSP, mal_uint32 frameCount, void* pF // A return value of 0 indicates an error. // // This function is useful for one-off bulk conversions, but if you're streaming data you should use the DSP APIs instead. -mal_uint32 mal_convert_frames(void* pOut, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, const void* pIn, mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_uint32 frameCountIn); - -// Helper for initializing a mal_dsp_config object. -mal_dsp_config mal_dsp_config_init(mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut); +mal_uint64 mal_convert_frames(void* pOut, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, const void* pIn, mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_uint64 frameCountIn); +mal_uint64 mal_convert_frames_ex(void* pOut, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, mal_channel channelMapOut[MAL_MAX_CHANNELS], const void* pIn, mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_uint64 frameCountIn); -/////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Utiltities // -/////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Creates a mutex. // @@ -1490,11 +2099,17 @@ void mal_mutex_unlock(mal_mutex* pMutex); -/////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Miscellaneous Helpers // -/////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Performs an aligned malloc, with the assumption that the alignment is a power of 2. +void* mal_aligned_malloc(size_t sz, size_t alignment); + +// Free's an aligned malloc'd buffer. +void mal_aligned_free(void* p); // Retrieves a friendly name for a backend. const char* mal_get_backend_name(mal_backend backend); @@ -1507,32 +2122,132 @@ void mal_blend_f32(float* pOut, float* pInA, float* pInB, float factor, mal_uint -/////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Format Conversion // -/////////////////////////////////////////////////////////////////////////////// -void mal_pcm_u8_to_s16(short* pOut, const unsigned char* pIn, unsigned int count); -void mal_pcm_u8_to_s24(void* pOut, const unsigned char* pIn, unsigned int count); -void mal_pcm_u8_to_s32(int* pOut, const unsigned char* pIn, unsigned int count); -void mal_pcm_u8_to_f32(float* pOut, const unsigned char* pIn, unsigned int count); -void mal_pcm_s16_to_u8(unsigned char* pOut, const short* pIn, unsigned int count); -void mal_pcm_s16_to_s24(void* pOut, const short* pIn, unsigned int count); -void mal_pcm_s16_to_s32(int* pOut, const short* pIn, unsigned int count); -void mal_pcm_s16_to_f32(float* pOut, const short* pIn, unsigned int count); -void mal_pcm_s24_to_u8(unsigned char* pOut, const void* pIn, unsigned int count); -void mal_pcm_s24_to_s16(short* pOut, const void* pIn, unsigned int count); -void mal_pcm_s24_to_s32(int* pOut, const void* pIn, unsigned int count); -void mal_pcm_s24_to_f32(float* pOut, const void* pIn, unsigned int count); -void mal_pcm_s32_to_u8(unsigned char* pOut, const int* pIn, unsigned int count); -void mal_pcm_s32_to_s16(short* pOut, const int* pIn, unsigned int count); -void mal_pcm_s32_to_s24(void* pOut, const int* pIn, unsigned int count); -void mal_pcm_s32_to_f32(float* pOut, const int* pIn, unsigned int count); -void mal_pcm_f32_to_u8(unsigned char* pOut, const float* pIn, unsigned int count); -void mal_pcm_f32_to_s16(short* pOut, const float* pIn, unsigned int count); -void mal_pcm_f32_to_s24(void* pOut, const float* pIn, unsigned int count); -void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count); -void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_format formatIn, unsigned int sampleCount); +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void mal_pcm_u8_to_s16(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_u8_to_s24(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_u8_to_s32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_u8_to_f32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s16_to_u8(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s16_to_s24(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s16_to_s32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s16_to_f32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s24_to_u8(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s24_to_s16(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s24_to_s32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s24_to_f32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s32_to_u8(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s32_to_s16(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s32_to_s24(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_s32_to_f32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_f32_to_u8(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_f32_to_s16(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +void mal_pcm_f32_to_s24(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); +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); + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Decoding +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#ifndef MAL_NO_DECODING + +typedef struct mal_decoder mal_decoder; + +typedef enum +{ + mal_seek_origin_start, + mal_seek_origin_current +} mal_seek_origin; + +typedef size_t (* mal_decoder_read_proc) (mal_decoder* pDecoder, void* pBufferOut, size_t bytesToRead); // Returns the number of bytes read. +typedef mal_bool32 (* mal_decoder_seek_proc) (mal_decoder* pDecoder, int byteOffset, mal_seek_origin origin); +typedef mal_result (* mal_decoder_seek_to_frame_proc)(mal_decoder* pDecoder, mal_uint64 frameIndex); +typedef mal_result (* mal_decoder_uninit_proc) (mal_decoder* pDecoder); + +typedef struct +{ + mal_format outputFormat; // Set to 0 or mal_format_unknown to use the stream's internal format. + mal_uint32 outputChannels; // Set to 0 to use the stream's internal channels. + mal_uint32 outputSampleRate; // Set to 0 to use the stream's internal sample rate. + mal_channel outputChannelMap[MAL_MAX_CHANNELS]; +} mal_decoder_config; + +struct mal_decoder +{ + mal_decoder_read_proc onRead; + mal_decoder_seek_proc onSeek; + void* pUserData; + mal_format internalFormat; + mal_uint32 internalChannels; + mal_uint32 internalSampleRate; + mal_channel internalChannelMap[MAL_MAX_CHANNELS]; + mal_format outputFormat; + mal_uint32 outputChannels; + mal_uint32 outputSampleRate; + mal_channel outputChannelMap[MAL_MAX_CHANNELS]; + mal_dsp dsp; // <-- Format conversion is achieved by running frames through this. + mal_decoder_seek_to_frame_proc onSeekToFrame; + mal_decoder_uninit_proc onUninit; + void* pInternalDecoder; // <-- The drwav/drflac/stb_vorbis/etc. objects. + struct + { + const mal_uint8* pData; + size_t dataSize; + size_t currentReadPos; + } memory; // Only used for decoders that were opened against a block of memory. +}; + +mal_decoder_config mal_decoder_config_init(mal_format outputFormat, mal_uint32 outputChannels, mal_uint32 outputSampleRate); + +mal_result mal_decoder_init(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder); +mal_result mal_decoder_init_wav(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder); +mal_result mal_decoder_init_flac(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder); +mal_result mal_decoder_init_vorbis(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder); +mal_result mal_decoder_init_mp3(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder); + +mal_result mal_decoder_init_memory(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder); +mal_result mal_decoder_init_memory_wav(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder); +mal_result mal_decoder_init_memory_flac(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder); +mal_result mal_decoder_init_memory_vorbis(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder); +mal_result mal_decoder_init_memory_mp3(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder); + +#ifndef MAL_NO_STDIO +mal_result mal_decoder_init_file(const char* pFilePath, const mal_decoder_config* pConfig, mal_decoder* pDecoder); +mal_result mal_decoder_init_file_wav(const char* pFilePath, const mal_decoder_config* pConfig, mal_decoder* pDecoder); +#endif + +mal_result mal_decoder_uninit(mal_decoder* pDecoder); + +mal_uint64 mal_decoder_read(mal_decoder* pDecoder, mal_uint64 frameCount, void* pFramesOut); +mal_result mal_decoder_seek_to_frame(mal_decoder* pDecoder, mal_uint64 frameIndex); + +#endif // MAL_NO_DECODING + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Generation +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + double amplitude; + double periodsPerSecond; + double delta; + double time; +} mal_sine_wave; + +mal_result mal_sine_wave_init(double amplitude, double period, mal_uint32 sampleRate, mal_sine_wave* pSignWave); +mal_uint64 mal_sine_wave_read(mal_sine_wave* pSignWave, mal_uint64 count, float* pSamples); + #ifdef __cplusplus } @@ -1549,6 +2264,8 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form /////////////////////////////////////////////////////////////////////////////// #ifdef MAL_IMPLEMENTATION #include +#include // For INT_MAX +#include // sin(), etc. #ifdef MAL_WIN32 #include @@ -1595,6 +2312,254 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form #endif #endif +// Architecture Detection +#if defined(__x86_64__) || defined(_M_X64) +#define MAL_X64 +#elif defined(__i386) || defined(_M_IX86) +#define MAL_X86 +#elif defined(__arm__) || defined(_M_ARM) +#define MAL_ARM +#endif + +// Intrinsics Support +#if defined(MAL_X64) || defined(MAL_X86) + #if defined(_MSC_VER) + // MSVC. + #if !defined(MAL_NO_SSE2) // Assume all MSVC compilers support SSE2 intrinsics. + #define MAL_SUPPORT_SSE2 + #endif + #if _MSC_VER >= 1600 && !defined(MAL_NO_AVX) // 2010 + #define MAL_SUPPORT_AVX + #endif + #if _MSC_VER >= 1910 && !defined(MAL_NO_AVX512) // 2017 + #define MAL_SUPPORT_AVX512 + #endif + #else + // Assume GNUC-style. + #if defined(__SSE2__) && !defined(MAL_NO_SSE2) + #define MAL_SUPPORT_SSE2 + #endif + #if defined(__AVX__) && !defined(MAL_NO_AVX) + #define MAL_SUPPORT_AVX + #endif + #if defined(__AVX512F__) && !defined(MAL_NO_AVX512) + #define MAL_SUPPORT_AVX512 + #endif + #endif + + // If at this point we still haven't determined compiler support for the intrinsics just fall back to __has_include. + #if !defined(__GNUC__) && defined(__has_include) + #if !defined(MAL_SUPPORT_SSE2) && !defined(MAL_NO_SSE2) && __has_include() + #define MAL_SUPPORT_SSE2 + #endif + #if !defined(MAL_SUPPORT_AVX) && !defined(MAL_NO_AVX) && __has_include() + #define MAL_SUPPORT_AVX + #endif + #if !defined(MAL_SUPPORT_AVX512) && !defined(MAL_NO_AVX512) && __has_include() + #define MAL_SUPPORT_AVX512 + #endif + #endif + + #if defined(MAL_SUPPORT_AVX512) + #include // Not a mistake. Intentionally including instead of because otherwise the compiler will complain. + #elif defined(MAL_SUPPORT_AVX) + #include + #elif defined(MAL_SUPPORT_SSE2) + #include + #endif +#endif + +#if defined(MAL_ARM) + #if !defined(MAL_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) + #define MAL_SUPPORT_NEON + #endif + + // Fall back to looking for the #include file. + #if !defined(__GNUC__) && defined(__has_include) + #if !defined(MAL_SUPPORT_NEON) && !defined(MAL_NO_NEON) && __has_include() + #define MAL_SUPPORT_NEON + #endif + #endif + + #if defined(MAL_SUPPORT_NEON) + #include + #endif +#endif + + +#if defined(MAL_X64) || defined(MAL_X86) + #if defined(_MSC_VER) + #if _MSC_VER >= 1400 + #include + static MAL_INLINE void mal_cpuid(int info[4], int fid) + { + __cpuid(info, fid); + } + #else + #define MAL_NO_CPUID + #endif + + #if _MSC_VER >= 1600 + static MAL_INLINE unsigned __int64 mal_xgetbv(int reg) + { + return _xgetbv(reg); + } + #else + #define MAL_NO_XGETBV + #endif + #elif defined(__GNUC__) || defined(__clang__) + static MAL_INLINE void mal_cpuid(int info[4], int fid) + { + asm ( + "movl %[fid], %%eax\n\t" + "cpuid\n\t" + "movl %%eax, %[info0]\n\t" + "movl %%ebx, %[info1]\n\t" + "movl %%ecx, %[info2]\n\t" + "movl %%edx, %[info3]\n\t" + : [info0] "=rm"(info[0]), + [info1] "=rm"(info[1]), + [info2] "=rm"(info[2]), + [info3] "=rm"(info[3]) + : [fid] "rm"(fid) + : "eax", "ebx", "ecx", "edx" + ); + } + + static MAL_INLINE unsigned long long mal_xgetbv(int reg) + { + unsigned int hi; + unsigned int lo; + + asm ( + "movl %[reg], %%ecx\n\t" + "xgetbv\n\t" + "movl %%eax, %[lo]\n\t" + "movl %%edx, %[hi]\n\t" + : [lo] "=rm"(lo), + [hi] "=rm"(hi) + : [reg] "rm"(reg) + : "eax", "ecx", "edx" + ); + + return ((unsigned long long)hi << 32ULL) | (unsigned long long)lo; + } + #else + #define MAL_NO_CPUID + #define MAL_NO_XGETBV + #endif +#else + #define MAL_NO_CPUID + #define MAL_NO_XGETBV +#endif + +static MAL_INLINE mal_bool32 mal_has_sse2() +{ +#if (defined(MAL_X64) || defined(MAL_X86)) && !defined(MAL_NO_SSE2) + #if defined(MAL_X64) + return MAL_TRUE; // 64-bit targets always support SSE2. + #elif (defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE2__) + return MAL_TRUE; // If the compiler is allowed to freely generate SSE2 code we can assume support. + #else + #if defined(MAL_NO_CPUID) + return MAL_FALSE; + #else + int info[4]; + mal_cpuid(info, 1); + return (info[3] & (1 << 26)) != 0; + #endif + #endif +#else + return MAL_FALSE; // SSE2 is only supported on x86 and x64 architectures. +#endif +} + +static MAL_INLINE mal_bool32 mal_has_avx() +{ +#if (defined(MAL_X64) || defined(MAL_X86)) && !defined(MAL_NO_AVX) + #if defined(_AVX_) || defined(__AVX__) + return MAL_TRUE; // If the compiler is allowed to freely generate AVX code we can assume support. + #else + // AVX requires both CPU and OS support. + #if defined(MAL_NO_CPUID) || defined(MAL_NO_XGETBV) + return MAL_FALSE; + #else + int info[4]; + mal_cpuid(info, 1); + if (((info[2] & (1 << 27)) != 0) && ((info[2] & (1 << 28)) != 0)) { + mal_uint64 xrc = mal_xgetbv(0); + if ((xrc & 0x06) == 0x06) { + return MAL_TRUE; + } else { + return MAL_FALSE; + } + } else { + return MAL_FALSE; + } + #endif + #endif +#else + return MAL_FALSE; // AVX is only supported on x86 and x64 architectures. +#endif +} + +static MAL_INLINE mal_bool32 mal_has_avx512f() +{ +#if (defined(MAL_X64) || defined(MAL_X86)) && !defined(MAL_NO_AVX512) + #if defined(__AVX512F__) + return MAL_TRUE; // If the compiler is allowed to freely generate AVX-512F code we can assume support. + #else + // AVX-512 requires both CPU and OS support. + #if defined(MAL_NO_CPUID) || defined(MAL_NO_XGETBV) + return MAL_FALSE; + #else + int info[4]; + mal_cpuid(info, 1); + if (((info[2] & (1 << 27)) != 0) && ((info[1] & (1 << 16)) != 0)) { + mal_uint64 xrc = mal_xgetbv(0); + if ((xrc & 0xE6) == 0xE6) { + return MAL_TRUE; + } else { + return MAL_FALSE; + } + } else { + return MAL_FALSE; + } + #endif + #endif +#else + return MAL_FALSE; // AVX-512F is only supported on x86 and x64 architectures. +#endif +} + +static MAL_INLINE mal_bool32 mal_has_neon() +{ +#if defined(MAL_ARM) && !defined(MAL_NO_NEON) + #if (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) + return MAL_TRUE; // If the compiler is allowed to freely generate NEON code we can assume support. + #else + // TODO: Runtime check. + return MAL_FALSE; + #endif +#else + return MAL_FALSE; // NEON is only supported on ARM architectures. +#endif +} + + +#ifndef MAL_PI +#define MAL_PI 3.14159265358979323846264f +#endif +#ifndef MAL_PI_D +#define MAL_PI_D 3.14159265358979323846264 +#endif + +// Unfortunately using runtime linking for pthreads causes problems. This has occurred for me when testing on FreeBSD. When +// using runtime linking, deadlocks can occur (for me it happens when loading data from fread()). It turns out that doing +// compile-time linking fixes this. I'm not sure why this happens, but the safest way I can think of to fix this is to simply +// disable runtime linking by default. To enable runtime linking, #define this before the implementation of this file. I am +// not officially supporting this, but I'm leaving it here in case it's useful for somebody, somewhere. +//#define MAL_USE_RUNTIME_LINKING_FOR_PTHREAD // Disable run-time linking on certain backends. #ifndef MAL_NO_RUNTIME_LINKING @@ -1606,29 +2571,41 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form // Check if we have the necessary development packages for each backend at the top so we can use this to determine whether or not // certain unused functions and variables can be excluded from the build to avoid warnings. #ifdef MAL_ENABLE_WASAPI - #define MAL_HAS_WASAPI - #ifdef __has_include - #if !__has_include() - #undef MAL_HAS_WASAPI - #endif - #endif + #define MAL_HAS_WASAPI // Every compiler should support WASAPI #endif #ifdef MAL_ENABLE_DSOUND - #define MAL_HAS_DSOUND - #ifdef __has_include - #if !__has_include() - #undef MAL_HAS_DSOUND - #endif - #endif + #define MAL_HAS_DSOUND // Every compiler should support DirectSound. #endif #ifdef MAL_ENABLE_WINMM - #define MAL_HAS_WINMM // Every compiler I'm aware of supports WinMM. + #define MAL_HAS_WINMM // Every compiler I'm aware of supports WinMM. #endif #ifdef MAL_ENABLE_ALSA #define MAL_HAS_ALSA - #ifdef __has_include - #if !__has_include() - #undef MAL_HAS_ALSA + #ifdef MAL_NO_RUNTIME_LINKING + #ifdef __has_include + #if !__has_include() + #undef MAL_HAS_ALSA + #endif + #endif + #endif +#endif +#ifdef MAL_ENABLE_PULSEAUDIO + #define MAL_HAS_PULSEAUDIO // Development packages are unnecessary for PulseAudio. + #ifdef MAL_NO_RUNTIME_LINKING + #ifdef __has_include + #if !__has_include() + #undef MAL_HAS_PULSEAUDIO + #endif + #endif + #endif +#endif +#ifdef MAL_ENABLE_JACK + #define MAL_HAS_JACK + #ifdef MAL_NO_RUNTIME_LINKING + #ifdef __has_include + #if !__has_include() + #undef MAL_HAS_JACK + #endif #endif #endif #endif @@ -1636,10 +2613,10 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form #define MAL_HAS_COREAUDIO #endif #ifdef MAL_ENABLE_OSS - #define MAL_HAS_OSS // OSS is the only supported backend for Unix and BSD, so it must be present else this library is useless. + #define MAL_HAS_OSS // OSS is the only supported backend for Unix and BSD, so it must be present else this library is useless. #endif #ifdef MAL_ENABLE_OPENSL - #define MAL_HAS_OPENSL // Like OSS, OpenSL is the only supported backend for Android. It must be present. + #define MAL_HAS_OPENSL // OpenSL is the only supported backend for Android. It must be present. #endif #ifdef MAL_ENABLE_OPENAL #define MAL_HAS_OPENAL @@ -1689,6 +2666,7 @@ typedef void (WINAPI * MAL_PFN_CoUninitialize)(); typedef HRESULT (WINAPI * MAL_PFN_CoCreateInstance)(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv); typedef void (WINAPI * MAL_PFN_CoTaskMemFree)(LPVOID pv); typedef HRESULT (WINAPI * MAL_PFN_PropVariantClear)(PROPVARIANT *pvar); +typedef int (WINAPI * MAL_PFN_StringFromGUID2)(const GUID* const rguid, LPOLESTR lpsz, int cchMax); typedef HWND (WINAPI * MAL_PFN_GetForegroundWindow)(); typedef HWND (WINAPI * MAL_PFN_GetDesktopWindow)(); @@ -1702,6 +2680,21 @@ typedef HWND (WINAPI * MAL_PFN_GetDesktopWindow)(); #define MAL_STATE_STOPPING 4 // Transitioning from a started state to stopped. +// The default format when mal_format_unknown (0) is requested when initializing a device. +#ifndef MAL_DEFAULT_FORMAT +#define MAL_DEFAULT_FORMAT mal_format_f32 +#endif + +// The default channel count to use when 0 is used when initializing a device. +#ifndef MAL_DEFAULT_CHANNELS +#define MAL_DEFAULT_CHANNELS 2 +#endif + +// The default sample rate to use when 0 is used when initializing a device. +#ifndef MAL_DEFAULT_SAMPLE_RATE +#define MAL_DEFAULT_SAMPLE_RATE 48000 +#endif + // The default size of the device's buffer in milliseconds. // // If this is too small you may get underruns and overruns in which case you'll need to either increase @@ -1715,6 +2708,30 @@ typedef HWND (WINAPI * MAL_PFN_GetDesktopWindow)(); #define MAL_DEFAULT_PERIODS 2 #endif +// Standard sample rates, in order of priority. +mal_uint32 g_malStandardSampleRatePriorities[] = { + MAL_SAMPLE_RATE_48000, // Most common + MAL_SAMPLE_RATE_44100, + + MAL_SAMPLE_RATE_32000, // Lows + MAL_SAMPLE_RATE_24000, + MAL_SAMPLE_RATE_22050, + + MAL_SAMPLE_RATE_88200, // Highs + MAL_SAMPLE_RATE_96000, + MAL_SAMPLE_RATE_176400, + MAL_SAMPLE_RATE_192000, + + MAL_SAMPLE_RATE_16000, // Extreme lows + MAL_SAMPLE_RATE_11025, + MAL_SAMPLE_RATE_8000, + + MAL_SAMPLE_RATE_352800, // Extreme highs + MAL_SAMPLE_RATE_384000 +}; + +#define MAL_DEFAULT_PLAYBACK_DEVICE_NAME "Default Playback Device" +#define MAL_DEFAULT_CAPTURE_DEVICE_NAME "Default Capture Device" /////////////////////////////////////////////////////////////////////////////// // @@ -1774,24 +2791,18 @@ typedef HWND (WINAPI * MAL_PFN_GetDesktopWindow)(); #define mal_countof(x) (sizeof(x) / sizeof(x[0])) #define mal_max(x, y) (((x) > (y)) ? (x) : (y)) #define mal_min(x, y) (((x) < (y)) ? (x) : (y)) +#define mal_offset_ptr(p, offset) (((mal_uint8*)(p)) + (offset)) + +#define mal_buffer_frame_capacity(buffer, channels, format) (sizeof(buffer) / mal_get_bytes_per_sample(format) / (channels)) -#define mal_buffer_frame_capacity(buffer, channels, format) (sizeof(buffer) / mal_get_sample_size_in_bytes(format) / (channels)) -// Some of these string utility functions are unused on some platforms. -#if defined(_MSC_VER) - #pragma warning(push) - #pragma warning(disable:4505) -#elif defined(__GNUC__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wunused-function" -#endif // Return Values: // 0: Success // 22: EINVAL // 34: ERANGE // // Not using symbolic constants for errors because I want to avoid #including errno.h -static int mal_strcpy_s(char* dst, size_t dstSizeInBytes, const char* src) +int mal_strcpy_s(char* dst, size_t dstSizeInBytes, const char* src) { if (dst == 0) { return 22; @@ -1818,7 +2829,7 @@ static int mal_strcpy_s(char* dst, size_t dstSizeInBytes, const char* src) return 34; } -static int mal_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count) +int mal_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count) { if (dst == 0) { return 22; @@ -1850,7 +2861,7 @@ static int mal_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size return 34; } -static int mal_strcat_s(char* dst, size_t dstSizeInBytes, const char* src) +int mal_strcat_s(char* dst, size_t dstSizeInBytes, const char* src) { if (dst == 0) { return 22; @@ -1890,7 +2901,7 @@ static int mal_strcat_s(char* dst, size_t dstSizeInBytes, const char* src) return 0; } -static int mal_itoa_s(int value, char* dst, size_t dstSizeInBytes, int radix) +int mal_itoa_s(int value, char* dst, size_t dstSizeInBytes, int radix) { if (dst == NULL || dstSizeInBytes == 0) { return 22; @@ -1956,7 +2967,7 @@ static int mal_itoa_s(int value, char* dst, size_t dstSizeInBytes, int radix) return 0; } -static int mal_strcmp(const char* str1, const char* str2) +int mal_strcmp(const char* str1, const char* str2) { if (str1 == str2) return 0; @@ -1979,15 +2990,10 @@ static int mal_strcmp(const char* str1, const char* str2) return ((unsigned char*)str1)[0] - ((unsigned char*)str2)[0]; } -#if defined(_MSC_VER) - #pragma warning(pop) -#elif defined(__GNUC__) - #pragma GCC diagnostic pop -#endif // Thanks to good old Bit Twiddling Hacks for this one: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 -static inline unsigned int mal_next_power_of_2(unsigned int x) +static MAL_INLINE unsigned int mal_next_power_of_2(unsigned int x) { x--; x |= x >> 1; @@ -2000,12 +3006,12 @@ static inline unsigned int mal_next_power_of_2(unsigned int x) return x; } -static inline unsigned int mal_prev_power_of_2(unsigned int x) +static MAL_INLINE unsigned int mal_prev_power_of_2(unsigned int x) { return mal_next_power_of_2(x) >> 1; } -static inline unsigned int mal_round_to_power_of_2(unsigned int x) +static MAL_INLINE unsigned int mal_round_to_power_of_2(unsigned int x) { unsigned int prev = mal_prev_power_of_2(x); unsigned int next = mal_next_power_of_2(x); @@ -2019,19 +3025,53 @@ static inline unsigned int mal_round_to_power_of_2(unsigned int x) // Clamps an f32 sample to -1..1 -static inline float mal_clip_f32(float x) +static MAL_INLINE float mal_clip_f32(float x) { if (x < -1) return -1; if (x > +1) return +1; return x; } -static inline float mal_mix_f32(float x, float y, float a) +static MAL_INLINE float mal_mix_f32(float x, float y, float a) { return x*(1-a) + y*a; } +// Splits a buffer into parts of equal length and of the given alignment. The returned size of the split buffers will be a +// multiple of the alignment. The alignment must be a power of 2. +void mal_split_buffer(void* pBuffer, size_t bufferSize, size_t splitCount, size_t alignment, void** ppBuffersOut, size_t* pSplitSizeOut) +{ + if (pBuffer == NULL || bufferSize == 0 || splitCount == 0) { + return; + } + + if (alignment == 0) { + alignment = 1; + } + + mal_uintptr pBufferUnaligned = (mal_uintptr)pBuffer; + mal_uintptr pBufferAligned = (pBufferUnaligned + (alignment-1)) & ~(alignment-1); + size_t unalignedBytes = (size_t)(pBufferAligned - pBufferUnaligned); + + size_t splitSize = 0; + if (bufferSize >= unalignedBytes) { + splitSize = (bufferSize - unalignedBytes) / splitCount; + splitSize = splitSize & ~(alignment-1); + } + + if (ppBuffersOut != NULL) { + for (size_t i = 0; i < splitCount; ++i) { + ppBuffersOut[i] = (mal_uint8*)(pBufferAligned + (splitSize*i)); + } + } + + if (pSplitSizeOut) { + *pSplitSizeOut = splitSize; + } +} + + /////////////////////////////////////////////////////////////////////////////// // // Atomics @@ -2065,7 +3105,7 @@ static inline float mal_mix_f32(float x, float y, float a) // /////////////////////////////////////////////////////////////////////////////// #ifdef MAL_WIN32 -static LARGE_INTEGER g_mal_TimerFrequency = {{0}}; +LARGE_INTEGER g_mal_TimerFrequency = {{0}}; void mal_timer_init(mal_timer* pTimer) { if (g_mal_TimerFrequency.QuadPart == 0) { @@ -2087,13 +3127,13 @@ double mal_timer_get_time_in_seconds(mal_timer* pTimer) return (counter.QuadPart - pTimer->counter) / (double)g_mal_TimerFrequency.QuadPart; } #elif defined(MAL_APPLE) && (__MAC_OS_X_VERSION_MIN_REQUIRED < 101200) -static uint64_t g_mal_TimerFrequency = 0; +uint64_t g_mal_TimerFrequency = 0; void mal_timer_init(mal_timer* pTimer) { mach_timebase_info_data_t baseTime; mach_timebase_info(&baseTime); g_mal_TimerFrequency = (baseTime.denom * 1e9) / baseTime.numer; - + pTimer->counter = mach_absolute_time(); } @@ -2101,7 +3141,7 @@ double mal_timer_get_time_in_seconds(mal_timer* pTimer) { uint64_t newTimeCounter = mach_absolute_time(); uint64_t oldTimeCounter = pTimer->counter; - + return (newTimeCounter - oldTimeCounter) / g_mal_TimerFrequency; } #else @@ -2175,15 +3215,29 @@ mal_proc mal_dlsym(mal_handle handle, const char* symbol) // /////////////////////////////////////////////////////////////////////////////// #ifdef MAL_WIN32 +int mal_thread_priority_to_win32(mal_thread_priority priority) +{ + switch (priority) { + case mal_thread_priority_idle: return THREAD_PRIORITY_IDLE; + case mal_thread_priority_lowest: return THREAD_PRIORITY_LOWEST; + case mal_thread_priority_low: return THREAD_PRIORITY_BELOW_NORMAL; + case mal_thread_priority_normal: return THREAD_PRIORITY_NORMAL; + case mal_thread_priority_high: return THREAD_PRIORITY_ABOVE_NORMAL; + case mal_thread_priority_highest: return THREAD_PRIORITY_HIGHEST; + case mal_thread_priority_realtime: return THREAD_PRIORITY_TIME_CRITICAL; + default: return mal_thread_priority_normal; + } +} + mal_result mal_thread_create__win32(mal_context* pContext, mal_thread* pThread, mal_thread_entry_proc entryProc, void* pData) { - (void)pContext; - pThread->win32.hThread = CreateThread(NULL, 0, entryProc, pData, 0, NULL); if (pThread->win32.hThread == NULL) { return MAL_FAILED_TO_CREATE_THREAD; } + SetThreadPriority((HANDLE)pThread->win32.hThread, mal_thread_priority_to_win32(pContext->config.threadPriority)); + return MAL_SUCCESS; } @@ -2256,6 +3310,8 @@ mal_bool32 mal_event_signal__win32(mal_event* pEvent) #ifdef MAL_POSIX +#include + typedef int (* mal_pthread_create_proc)(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); typedef int (* mal_pthread_join_proc)(pthread_t thread, void **retval); typedef int (* mal_pthread_mutex_init_proc)(pthread_mutex_t *__mutex, const pthread_mutexattr_t *__mutexattr); @@ -2266,10 +3322,67 @@ typedef int (* mal_pthread_cond_init_proc)(pthread_cond_t *__restrict __cond, co typedef int (* mal_pthread_cond_destroy_proc)(pthread_cond_t *__cond); typedef int (* mal_pthread_cond_signal_proc)(pthread_cond_t *__cond); typedef int (* mal_pthread_cond_wait_proc)(pthread_cond_t *__restrict __cond, pthread_mutex_t *__restrict __mutex); +typedef int (* mal_pthread_attr_init_proc)(pthread_attr_t *attr); +typedef int (* mal_pthread_attr_destroy_proc)(pthread_attr_t *attr); +typedef int (* mal_pthread_attr_setschedpolicy_proc)(pthread_attr_t *attr, int policy); +typedef int (* mal_pthread_attr_getschedparam_proc)(const pthread_attr_t *attr, struct sched_param *param); +typedef int (* mal_pthread_attr_setschedparam_proc)(pthread_attr_t *attr, const struct sched_param *param); mal_bool32 mal_thread_create__posix(mal_context* pContext, mal_thread* pThread, mal_thread_entry_proc entryProc, void* pData) { - int result = ((mal_pthread_create_proc)pContext->posix.pthread_create)(&pThread->posix.thread, NULL, entryProc, pData); + pthread_attr_t* pAttr = NULL; + + // Try setting the thread priority. It's not critical if anything fails here. + pthread_attr_t attr; + if (((mal_pthread_attr_init_proc)pContext->posix.pthread_attr_init)(&attr) == 0) { + int scheduler = -1; + if (pContext->config.threadPriority == mal_thread_priority_idle) { +#ifdef SCHED_IDLE + if (((mal_pthread_attr_setschedpolicy_proc)pContext->posix.pthread_attr_setschedpolicy)(&attr, SCHED_IDLE) == 0) { + scheduler = SCHED_IDLE; + } +#endif + } else if (pContext->config.threadPriority == mal_thread_priority_realtime) { +#ifdef SCHED_FIFO + if (((mal_pthread_attr_setschedpolicy_proc)pContext->posix.pthread_attr_setschedpolicy)(&attr, SCHED_FIFO) == 0) { + scheduler = SCHED_FIFO; + } +#endif + } else { + scheduler = sched_getscheduler(0); + } + + if (scheduler != -1) { + int priorityMin = sched_get_priority_min(scheduler); + int priorityMax = sched_get_priority_max(scheduler); + int priorityStep = (priorityMax - priorityMin) / 7; // 7 = number of priorities supported by mini_al. + + struct sched_param sched; + if (((mal_pthread_attr_getschedparam_proc)pContext->posix.pthread_attr_getschedparam)(&attr, &sched) == 0) { + if (pContext->config.threadPriority == mal_thread_priority_idle) { + sched.sched_priority = priorityMin; + } else if (pContext->config.threadPriority == mal_thread_priority_realtime) { + sched.sched_priority = priorityMax; + } else { + sched.sched_priority += ((int)pContext->config.threadPriority + 5) * priorityStep; // +5 because the lowest priority is -5. + if (sched.sched_priority < priorityMin) { + sched.sched_priority = priorityMin; + } + if (sched.sched_priority > priorityMax) { + sched.sched_priority = priorityMax; + } + } + + if (((mal_pthread_attr_setschedparam_proc)pContext->posix.pthread_attr_setschedparam)(&attr, &sched) == 0) { + pAttr = &attr; + } + } + } + + ((mal_pthread_attr_destroy_proc)pContext->posix.pthread_attr_destroy)(&attr); + } + + int result = ((mal_pthread_create_proc)pContext->posix.pthread_create)(&pThread->posix.thread, pAttr, entryProc, pData); if (result != 0) { return MAL_FAILED_TO_CREATE_THREAD; } @@ -2401,7 +3514,9 @@ void mal_sleep(mal_uint32 milliseconds) mal_result mal_mutex_init(mal_context* pContext, mal_mutex* pMutex) { - if (pContext == NULL || pMutex == NULL) return MAL_INVALID_ARGS; + if (pContext == NULL || pMutex == NULL) { + return MAL_INVALID_ARGS; + } pMutex->pContext = pContext; @@ -2501,8 +3616,38 @@ mal_bool32 mal_event_signal(mal_event* pEvent) } +mal_uint32 mal_get_best_sample_rate_within_range(mal_uint32 sampleRateMin, mal_uint32 sampleRateMax) +{ + // Normalize the range in case we were given something stupid. + if (sampleRateMin < MAL_MIN_SAMPLE_RATE) { + sampleRateMin = MAL_MIN_SAMPLE_RATE; + } + if (sampleRateMax > MAL_MAX_SAMPLE_RATE) { + sampleRateMax = MAL_MAX_SAMPLE_RATE; + } + if (sampleRateMin > sampleRateMax) { + sampleRateMin = sampleRateMax; + } + + if (sampleRateMin == sampleRateMax) { + return sampleRateMax; + } else { + for (size_t iStandardRate = 0; iStandardRate < mal_countof(g_malStandardSampleRatePriorities); ++iStandardRate) { + mal_uint32 standardRate = g_malStandardSampleRatePriorities[iStandardRate]; + if (standardRate >= sampleRateMin && standardRate <= sampleRateMax) { + return standardRate; + } + } + } + + // Should never get here. + mal_assert(MAL_FALSE); + return 0; +} + + // Posts a log message. -static void mal_log(mal_context* pContext, mal_device* pDevice, const char* message) +void mal_log(mal_context* pContext, mal_device* pDevice, const char* message) { if (pContext == NULL) return; @@ -2513,7 +3658,7 @@ static void mal_log(mal_context* pContext, mal_device* pDevice, const char* mess } // Posts an error. Throw a breakpoint in here if you're needing to debug. The return value is always "resultCode". -static mal_result mal_context_post_error(mal_context* pContext, mal_device* pDevice, const char* message, mal_result resultCode) +mal_result mal_context_post_error(mal_context* pContext, mal_device* pDevice, const char* message, mal_result resultCode) { // Derive the context from the device if necessary. if (pContext == NULL) { @@ -2526,85 +3671,14 @@ static mal_result mal_context_post_error(mal_context* pContext, mal_device* pDev return resultCode; } -static mal_result mal_post_error(mal_device* pDevice, const char* message, mal_result resultCode) +mal_result mal_post_error(mal_device* pDevice, const char* message, mal_result resultCode) { return mal_context_post_error(NULL, pDevice, message, resultCode); } -#if !defined(MAL_ANDROID) -static void mal_get_default_channel_mapping(mal_backend backend, mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]) -{ - if (channels == 1) { // Mono - channelMap[0] = MAL_CHANNEL_FRONT_CENTER; - } else if (channels == 2) { // Stereo - channelMap[0] = MAL_CHANNEL_FRONT_LEFT; - channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; - } else if (channels == 3) { // 2.1 - channelMap[0] = MAL_CHANNEL_FRONT_LEFT; - channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; - channelMap[2] = MAL_CHANNEL_LFE; - } else if (channels == 4) { // 4.0 - channelMap[0] = MAL_CHANNEL_FRONT_LEFT; - channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; - channelMap[2] = MAL_CHANNEL_SIDE_LEFT; - channelMap[3] = MAL_CHANNEL_SIDE_RIGHT; - } else if (channels == 5) { // Not sure about this one. 4.1? - channelMap[0] = MAL_CHANNEL_FRONT_LEFT; - channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; - channelMap[2] = MAL_CHANNEL_SIDE_LEFT; - channelMap[3] = MAL_CHANNEL_SIDE_RIGHT; - channelMap[4] = MAL_CHANNEL_LFE; - } else if (channels >= 6) { // 5.1 - // Some backends use different default layouts. - if (backend == mal_backend_wasapi || backend == mal_backend_dsound || backend == mal_backend_winmm || backend == mal_backend_oss) { - channelMap[0] = MAL_CHANNEL_FRONT_LEFT; - channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; - channelMap[2] = MAL_CHANNEL_FRONT_CENTER; - channelMap[3] = MAL_CHANNEL_LFE; - channelMap[4] = MAL_CHANNEL_SIDE_LEFT; - channelMap[5] = MAL_CHANNEL_SIDE_RIGHT; - } else { - channelMap[0] = MAL_CHANNEL_FRONT_LEFT; - channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; - channelMap[2] = MAL_CHANNEL_SIDE_LEFT; - channelMap[3] = MAL_CHANNEL_SIDE_RIGHT; - channelMap[4] = MAL_CHANNEL_FRONT_CENTER; - channelMap[5] = MAL_CHANNEL_LFE; - } - - if (channels == 7) { // Not sure about this one. - channelMap[6] = MAL_CHANNEL_BACK_CENTER; - } else { - // I don't know what mapping to use in this case, but I'm making it upwards compatible with 7.1. Good luck! - mal_assert(channels >= 8); - channelMap[6] = MAL_CHANNEL_BACK_LEFT; - channelMap[7] = MAL_CHANNEL_BACK_RIGHT; - - // Beyond 7.1 I'm just guessing... - if (channels == 9) { - channelMap[8] = MAL_CHANNEL_BACK_CENTER; - } else if (channels == 10) { - channelMap[8] = MAL_CHANNEL_FRONT_LEFT_CENTER; - channelMap[9] = MAL_CHANNEL_FRONT_RIGHT_CENTER; - } else if (channels == 11) { - channelMap[ 8] = MAL_CHANNEL_FRONT_LEFT_CENTER; - channelMap[ 9] = MAL_CHANNEL_FRONT_RIGHT_CENTER; - channelMap[10] = MAL_CHANNEL_BACK_CENTER; - } else { - mal_assert(channels >= 12); - for (mal_uint8 iChannel = 11; iChannel < channels && iChannel < MAL_MAX_CHANNELS; ++iChannel) { - channelMap[iChannel] = iChannel + 1; - } - } - } - } -} -#endif - - // The callback for reading from the client -> DSP -> device. -static inline mal_uint32 mal_device__on_read_from_client(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut, void* pUserData) +mal_uint32 mal_device__on_read_from_client(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut, void* pUserData) { (void)pDSP; @@ -2620,7 +3694,7 @@ static inline mal_uint32 mal_device__on_read_from_client(mal_dsp* pDSP, mal_uint } // The callback for reading from the device -> DSP -> client. -static inline mal_uint32 mal_device__on_read_from_device(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut, void* pUserData) +mal_uint32 mal_device__on_read_from_device(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut, void* pUserData) { (void)pDSP; @@ -2636,7 +3710,7 @@ static inline mal_uint32 mal_device__on_read_from_device(mal_dsp* pDSP, mal_uint framesToRead = pDevice->_dspFrameCount; } - mal_uint32 bytesToRead = framesToRead * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat); + mal_uint32 bytesToRead = framesToRead * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat); mal_copy_memory(pFramesOut, pDevice->_dspFrames, bytesToRead); pDevice->_dspFrameCount -= framesToRead; pDevice->_dspFrames += bytesToRead; @@ -2646,16 +3720,16 @@ static inline mal_uint32 mal_device__on_read_from_device(mal_dsp* pDSP, mal_uint // A helper function for reading sample data from the client. Returns the number of samples read from the client. Remaining samples // are filled with silence. -static inline mal_uint32 mal_device__read_frames_from_client(mal_device* pDevice, mal_uint32 frameCount, void* pSamples) +static MAL_INLINE mal_uint32 mal_device__read_frames_from_client(mal_device* pDevice, mal_uint32 frameCount, void* pSamples) { mal_assert(pDevice != NULL); mal_assert(frameCount > 0); mal_assert(pSamples != NULL); - mal_uint32 framesRead = mal_dsp_read_frames(&pDevice->dsp, frameCount, pSamples); - mal_uint32 samplesRead = framesRead * pDevice->internalChannels; - mal_uint32 sampleSize = mal_get_sample_size_in_bytes(pDevice->internalFormat); - mal_uint32 consumedBytes = samplesRead*sampleSize; + mal_uint32 framesRead = (mal_uint32)mal_dsp_read(&pDevice->dsp, frameCount, pSamples, pDevice->dsp.pUserData); + mal_uint32 samplesRead = framesRead * pDevice->internalChannels; + mal_uint32 sampleSize = mal_get_bytes_per_sample(pDevice->internalFormat); + mal_uint32 consumedBytes = samplesRead*sampleSize; mal_uint32 remainingBytes = ((frameCount * pDevice->internalChannels) - samplesRead)*sampleSize; mal_zero_memory((mal_uint8*)pSamples + consumedBytes, remainingBytes); @@ -2663,7 +3737,7 @@ static inline mal_uint32 mal_device__read_frames_from_client(mal_device* pDevice } // A helper for sending sample data to the client. -static inline void mal_device__send_frames_to_client(mal_device* pDevice, mal_uint32 frameCount, const void* pSamples) +static MAL_INLINE void mal_device__send_frames_to_client(mal_device* pDevice, mal_uint32 frameCount, const void* pSamples) { mal_assert(pDevice != NULL); mal_assert(frameCount > 0); @@ -2675,10 +3749,10 @@ static inline void mal_device__send_frames_to_client(mal_device* pDevice, mal_ui pDevice->_dspFrames = (const mal_uint8*)pSamples; mal_uint8 chunkBuffer[4096]; - mal_uint32 chunkFrameCount = sizeof(chunkBuffer) / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels; + mal_uint32 chunkFrameCount = sizeof(chunkBuffer) / mal_get_bytes_per_sample(pDevice->format) / pDevice->channels; for (;;) { - mal_uint32 framesJustRead = mal_dsp_read_frames(&pDevice->dsp, chunkFrameCount, chunkBuffer); + mal_uint32 framesJustRead = (mal_uint32)mal_dsp_read(&pDevice->dsp, chunkFrameCount, chunkBuffer, pDevice->dsp.pUserData); if (framesJustRead == 0) { break; } @@ -2693,32 +3767,73 @@ static inline void mal_device__send_frames_to_client(mal_device* pDevice, mal_ui } // A helper for changing the state of the device. -static inline void mal_device__set_state(mal_device* pDevice, mal_uint32 newState) +static MAL_INLINE void mal_device__set_state(mal_device* pDevice, mal_uint32 newState) { mal_atomic_exchange_32(&pDevice->state, newState); } // A helper for getting the state of the device. -static inline mal_uint32 mal_device__get_state(mal_device* pDevice) +static MAL_INLINE mal_uint32 mal_device__get_state(mal_device* pDevice) { return pDevice->state; } #ifdef MAL_WIN32 - #if defined(MAL_HAS_WASAPI) || defined(MAL_HAS_DSOUND) - static GUID MAL_GUID_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; - static GUID MAL_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; - //static GUID MAL_GUID_KSDATAFORMAT_SUBTYPE_ALAW = {0x00000006, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; - //static GUID MAL_GUID_KSDATAFORMAT_SUBTYPE_MULAW = {0x00000007, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; - #endif + GUID MAL_GUID_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; + GUID MAL_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; + //GUID MAL_GUID_KSDATAFORMAT_SUBTYPE_ALAW = {0x00000006, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; + //GUID MAL_GUID_KSDATAFORMAT_SUBTYPE_MULAW = {0x00000007, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; #endif +mal_bool32 mal_context__device_id_equal(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) +{ + mal_assert(pContext != NULL); + + if (pID0 == pID1) return MAL_TRUE; + + if ((pID0 == NULL && pID1 != NULL) || + (pID0 != NULL && pID1 == NULL)) { + return MAL_FALSE; + } + + if (pContext->onDeviceIDEqual) { + return pContext->onDeviceIDEqual(pContext, pID0, pID1); + } + + return MAL_FALSE; +} + + +typedef struct +{ + mal_device_type deviceType; + const mal_device_id* pDeviceID; + char* pName; + size_t nameBufferSize; + mal_bool32 foundDevice; +} mal_context__try_get_device_name_by_id__enum_callback_data; + +mal_bool32 mal_context__try_get_device_name_by_id__enum_callback(mal_context* pContext, mal_device_type deviceType, const mal_device_info* pDeviceInfo, void* pUserData) +{ + mal_context__try_get_device_name_by_id__enum_callback_data* pData = (mal_context__try_get_device_name_by_id__enum_callback_data*)pUserData; + mal_assert(pData != NULL); + + if (pData->deviceType == deviceType) { + if (pContext->onDeviceIDEqual(pContext, pData->pDeviceID, &pDeviceInfo->id)) { + mal_strncpy_s(pData->pName, pData->nameBufferSize, pDeviceInfo->name, (size_t)-1); + pData->foundDevice = MAL_TRUE; + } + } + + return !pData->foundDevice; +} + // Generic function for retrieving the name of a device by it's ID. // // This function simply enumerates every device and then retrieves the name of the first device that has the same ID. -static mal_result mal_context__try_get_device_name_by_id(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, char* pName, size_t nameBufferSize) +mal_result mal_context__try_get_device_name_by_id(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, char* pName, size_t nameBufferSize) { mal_assert(pContext != NULL); mal_assert(pName != NULL); @@ -2727,126 +3842,22 @@ static mal_result mal_context__try_get_device_name_by_id(mal_context* pContext, return MAL_NO_DEVICE; } - mal_uint32 deviceCount; - mal_result result = mal_enumerate_devices(pContext, type, &deviceCount, NULL); + mal_context__try_get_device_name_by_id__enum_callback_data data; + data.deviceType = type; + data.pDeviceID = pDeviceID; + data.pName = pName; + data.nameBufferSize = nameBufferSize; + data.foundDevice = MAL_FALSE; + mal_result result = mal_context_enumerate_devices(pContext, mal_context__try_get_device_name_by_id__enum_callback, &data); if (result != MAL_SUCCESS) { return result; } - mal_device_info* pInfos = (mal_device_info*)mal_malloc(sizeof(*pInfos) * deviceCount); - if (pInfos == NULL) { - return MAL_OUT_OF_MEMORY; + if (!data.foundDevice) { + return MAL_NO_DEVICE; + } else { + return MAL_SUCCESS; } - - result = mal_enumerate_devices(pContext, type, &deviceCount, pInfos); - if (result != MAL_SUCCESS) { - mal_free(pInfos); - return result; - } - - mal_bool32 found = MAL_FALSE; - for (mal_uint32 iDevice = 0; iDevice < deviceCount; ++iDevice) { - // Prefer backend specific comparisons for efficiency and accuracy, but fall back to a generic method if a backend-specific comparison - // is not implemented. - switch (pContext->backend) - { - #ifdef MAL_HAS_WASAPI - case mal_backend_wasapi: - { - if (memcmp(pDeviceID->wasapi, &pInfos[iDevice].id.wasapi, sizeof(pDeviceID->wasapi)) == 0) { - found = MAL_TRUE; - } - } break; - #endif - #ifdef MAL_HAS_DSOUND - case mal_backend_dsound: - { - if (memcmp(pDeviceID->dsound, &pInfos[iDevice].id.dsound, sizeof(pDeviceID->dsound)) == 0) { - found = MAL_TRUE; - } - } break; - #endif - #ifdef MAL_HAS_WINMM - case mal_backend_winmm: - { - if (pInfos[iDevice].id.winmm == pDeviceID->winmm) { - found = MAL_TRUE; - } - } break; - #endif - #ifdef MAL_HAS_ALSA - case mal_backend_alsa: - { - if (mal_strcmp(pInfos[iDevice].id.alsa, pDeviceID->alsa) == 0) { - found = MAL_TRUE; - } - } break; - #endif - #ifdef MAL_HAS_COREAUDIO - //case mal_backend_coreaudio: - //{ - // // TODO: Implement me. - //} break; - #endif - #ifdef MAL_HAS_OSS - case mal_backend_oss: - { - if (mal_strcmp(pInfos[iDevice].id.oss, pDeviceID->oss) == 0) { - found = MAL_TRUE; - } - } break; - #endif - #ifdef MAL_HAS_OPENSL - case mal_backend_opensl: - { - if (pInfos[iDevice].id.opensl == pDeviceID->opensl) { - found = MAL_TRUE; - } - } break; - #endif - #ifdef MAL_HAS_OPENAL - case mal_backend_openal: - { - if (mal_strcmp(pInfos[iDevice].id.openal, pDeviceID->openal) == 0) { - found = MAL_TRUE; - } - } break; - #endif - #ifdef MAL_HAS_SDL - case mal_backend_sdl: - { - if (pInfos[iDevice].id.sdl == pDeviceID->sdl) { - found = MAL_TRUE; - } - } break; - #endif - #ifdef MAL_HAS_NULL - case mal_backend_null: - { - if (pInfos[iDevice].id.nullbackend == pDeviceID->nullbackend) { - found = MAL_TRUE; - } - } break; - #endif - - // Fall back to a generic memory comparison. - default: - { - if (memcmp(pDeviceID, &pInfos[iDevice].id, sizeof(*pDeviceID)) == 0) { - found = MAL_TRUE; - } - } break; - } - - if (found) { - mal_strncpy_s(pName, nameBufferSize, pInfos[iDevice].name, (size_t)-1); - result = MAL_SUCCESS; - break; - } - } - - mal_free(pInfos); - return result; } @@ -2856,12 +3867,73 @@ static mal_result mal_context__try_get_device_name_by_id(mal_context* pContext, // /////////////////////////////////////////////////////////////////////////////// #ifdef MAL_HAS_NULL + +mal_bool32 mal_context_is_device_id_equal__null(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) +{ + mal_assert(pContext != NULL); + mal_assert(pID0 != NULL); + mal_assert(pID1 != NULL); + (void)pContext; + + return pID0->nullbackend == pID1->nullbackend; +} + +mal_result mal_context_enumerate_devices__null(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +{ + mal_assert(pContext != NULL); + mal_assert(callback != NULL); + + mal_bool32 cbResult = MAL_TRUE; + + // Playback. + if (cbResult) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "NULL Playback Device", (size_t)-1); + cbResult = callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); + } + + // Capture. + if (cbResult) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "NULL Capture Device", (size_t)-1); + cbResult = callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); + } + + return MAL_SUCCESS; +} + +mal_result mal_context_get_device_info__null(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +{ + mal_assert(pContext != NULL); + + (void)pContext; + (void)shareMode; + + if (pDeviceID != NULL && pDeviceID->nullbackend != 0) { + return MAL_NO_DEVICE; // Don't know the device. + } + + // Name / Description + if (deviceType == mal_device_type_playback) { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), "NULL Playback Device", (size_t)-1); + } else { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), "NULL Capture Device", (size_t)-1); + } + + return MAL_SUCCESS; +} + mal_result mal_context_init__null(mal_context* pContext) { mal_assert(pContext != NULL); + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__null; + pContext->onEnumDevices = mal_context_enumerate_devices__null; + pContext->onGetDeviceInfo = mal_context_get_device_info__null; + // The null backend always works. - (void)pContext; return MAL_SUCCESS; } @@ -2874,33 +3946,13 @@ mal_result mal_context_uninit__null(mal_context* pContext) return MAL_SUCCESS; } -static mal_result mal_enumerate_devices__null(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) -{ - (void)pContext; - - mal_uint32 infoSize = *pCount; - *pCount = 1; // There's only one "device" each for playback and recording for the null backend. - - if (pInfo != NULL && infoSize > 0) { - mal_zero_object(pInfo); - - if (type == mal_device_type_playback) { - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "NULL Playback Device", (size_t)-1); - } else { - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "NULL Capture Device", (size_t)-1); - } - } - - return MAL_SUCCESS; -} - -static void mal_device_uninit__null(mal_device* pDevice) +void mal_device_uninit__null(mal_device* pDevice) { mal_assert(pDevice != NULL); mal_free(pDevice->null_device.pBuffer); } -static mal_result mal_device_init__null(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +mal_result mal_device_init__null(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) { (void)pContext; (void)type; @@ -2912,7 +3964,13 @@ static mal_result mal_device_init__null(mal_context* pContext, mal_device_type t pDevice->bufferSizeInFrames = pConfig->bufferSizeInFrames; pDevice->periods = pConfig->periods; - pDevice->null_device.pBuffer = (mal_uint8*)mal_malloc(pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format)); + if (type == mal_device_type_playback) { + mal_strncpy_s(pDevice->name, sizeof(pDevice->name), "NULL Playback Device", (size_t)-1); + } else { + mal_strncpy_s(pDevice->name, sizeof(pDevice->name), "NULL Capture Device", (size_t)-1); + } + + pDevice->null_device.pBuffer = (mal_uint8*)mal_malloc(pDevice->bufferSizeInFrames * pDevice->channels * mal_get_bytes_per_sample(pDevice->format)); if (pDevice->null_device.pBuffer == NULL) { return MAL_OUT_OF_MEMORY; } @@ -2922,7 +3980,7 @@ static mal_result mal_device_init__null(mal_context* pContext, mal_device_type t return MAL_SUCCESS; } -static mal_result mal_device__start_backend__null(mal_device* pDevice) +mal_result mal_device__start_backend__null(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -2932,7 +3990,7 @@ static mal_result mal_device__start_backend__null(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__stop_backend__null(mal_device* pDevice) +mal_result mal_device__stop_backend__null(mal_device* pDevice) { mal_assert(pDevice != NULL); (void)pDevice; @@ -2940,7 +3998,7 @@ static mal_result mal_device__stop_backend__null(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__break_main_loop__null(mal_device* pDevice) +mal_result mal_device__break_main_loop__null(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -2948,7 +4006,7 @@ static mal_result mal_device__break_main_loop__null(mal_device* pDevice) return MAL_SUCCESS; } -static mal_bool32 mal_device__get_current_frame__null(mal_device* pDevice, mal_uint32* pCurrentPos) +mal_bool32 mal_device__get_current_frame__null(mal_device* pDevice, mal_uint32* pCurrentPos) { mal_assert(pDevice != NULL); mal_assert(pCurrentPos != NULL); @@ -2960,7 +4018,7 @@ static mal_bool32 mal_device__get_current_frame__null(mal_device* pDevice, mal_u return MAL_TRUE; } -static mal_uint32 mal_device__get_available_frames__null(mal_device* pDevice) +mal_uint32 mal_device__get_available_frames__null(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -3001,7 +4059,7 @@ static mal_uint32 mal_device__get_available_frames__null(mal_device* pDevice) } } -static mal_uint32 mal_device__wait_for_frames__null(mal_device* pDevice) +mal_uint32 mal_device__wait_for_frames__null(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -3018,7 +4076,7 @@ static mal_uint32 mal_device__wait_for_frames__null(mal_device* pDevice) return mal_device__get_available_frames__null(pDevice); } -static mal_result mal_device__main_loop__null(mal_device* pDevice) +mal_result mal_device__main_loop__null(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -3039,8 +4097,8 @@ static mal_result mal_device__main_loop__null(mal_device* pDevice) } mal_uint32 sampleCount = framesAvailable * pDevice->channels; - mal_uint32 lockOffset = pDevice->null_device.lastProcessedFrame * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format); - mal_uint32 lockSize = sampleCount * mal_get_sample_size_in_bytes(pDevice->format); + mal_uint32 lockOffset = pDevice->null_device.lastProcessedFrame * pDevice->channels * mal_get_bytes_per_sample(pDevice->format); + mal_uint32 lockSize = sampleCount * mal_get_bytes_per_sample(pDevice->format); if (pDevice->type == mal_device_type_playback) { if (pDevice->null_device.breakFromMainLoop) { @@ -3081,10 +4139,31 @@ static mal_result mal_device__main_loop__null(mal_device* pDevice) #define mal_CoTaskMemFree(pContext, pv) CoTaskMemFree(pv) #define mal_PropVariantClear(pContext, pvar) PropVariantClear(pvar) #endif + +// There's a few common headers for Win32 backends which include here for simplicity. Note that we should never +// include any files that do not come standard with modern compilers, and we may need to manually define a few +// symbols. +#include +#include + +#if !defined(MAXULONG_PTR) +typedef size_t DWORD_PTR; #endif -#if defined(MAL_HAS_WASAPI) || defined(MAL_HAS_DSOUND) -#include +#if !defined(WAVE_FORMAT_44M08) +#define WAVE_FORMAT_44M08 0x00000100 +#define WAVE_FORMAT_44S08 0x00000200 +#define WAVE_FORMAT_44M16 0x00000400 +#define WAVE_FORMAT_44S16 0x00000800 +#define WAVE_FORMAT_48M08 0x00001000 +#define WAVE_FORMAT_48S08 0x00002000 +#define WAVE_FORMAT_48M16 0x00004000 +#define WAVE_FORMAT_48S16 0x00008000 +#define WAVE_FORMAT_96M08 0x00010000 +#define WAVE_FORMAT_96S08 0x00020000 +#define WAVE_FORMAT_96M16 0x00040000 +#define WAVE_FORMAT_96S16 0x00080000 +#endif #ifndef SPEAKER_FRONT_LEFT #define SPEAKER_FRONT_LEFT 0x1 @@ -3128,8 +4207,10 @@ typedef struct #define WAVE_FORMAT_EXTENSIBLE 0xFFFE #endif +GUID MAL_GUID_NULL = {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + // Converts an individual Win32-style channel identifier (SPEAKER_FRONT_LEFT, etc.) to mini_al. -static mal_uint8 mal_channel_id_to_mal__win32(DWORD id) +mal_uint8 mal_channel_id_to_mal__win32(DWORD id) { switch (id) { @@ -3156,10 +4237,11 @@ static mal_uint8 mal_channel_id_to_mal__win32(DWORD id) } // Converts an individual mini_al channel identifier (MAL_CHANNEL_FRONT_LEFT, etc.) to Win32-style. -static DWORD mal_channel_id_to_win32(DWORD id) +DWORD mal_channel_id_to_win32(DWORD id) { switch (id) { + case MAL_CHANNEL_MONO: return SPEAKER_FRONT_CENTER; case MAL_CHANNEL_FRONT_LEFT: return SPEAKER_FRONT_LEFT; case MAL_CHANNEL_FRONT_RIGHT: return SPEAKER_FRONT_RIGHT; case MAL_CHANNEL_FRONT_CENTER: return SPEAKER_FRONT_CENTER; @@ -3183,7 +4265,7 @@ static DWORD mal_channel_id_to_win32(DWORD id) } // Converts a channel mapping to a Win32-style channel mask. -static DWORD mal_channel_map_to_channel_mask__win32(const mal_uint8 channelMap[MAL_MAX_CHANNELS], mal_uint32 channels) +DWORD mal_channel_map_to_channel_mask__win32(const mal_channel channelMap[MAL_MAX_CHANNELS], mal_uint32 channels) { DWORD dwChannelMask = 0; for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { @@ -3194,26 +4276,97 @@ static DWORD mal_channel_map_to_channel_mask__win32(const mal_uint8 channelMap[M } // Converts a Win32-style channel mask to a mini_al channel map. -static void mal_channel_mask_to_channel_map__win32(DWORD dwChannelMask, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS]) +void mal_channel_mask_to_channel_map__win32(DWORD dwChannelMask, mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]) { if (channels == 1 && dwChannelMask == 0) { - channelMap[0] = MAL_CHANNEL_FRONT_CENTER; + channelMap[0] = MAL_CHANNEL_MONO; } else if (channels == 2 && dwChannelMask == 0) { channelMap[0] = MAL_CHANNEL_FRONT_LEFT; channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; } else { - // Just iterate over each bit. - mal_uint32 iChannel = 0; - for (mal_uint32 iBit = 0; iBit < 32; ++iBit) { - DWORD bitValue = (dwChannelMask & (1 << iBit)); - if (bitValue != 0) { - // The bit is set. - channelMap[iChannel] = mal_channel_id_to_mal__win32(bitValue); - iChannel += 1; + if (channels == 1 && (dwChannelMask & SPEAKER_FRONT_CENTER) != 0) { + channelMap[0] = MAL_CHANNEL_MONO; + } else { + // Just iterate over each bit. + mal_uint32 iChannel = 0; + for (mal_uint32 iBit = 0; iBit < 32; ++iBit) { + DWORD bitValue = (dwChannelMask & (1UL << iBit)); + if (bitValue != 0) { + // The bit is set. + channelMap[iChannel] = mal_channel_id_to_mal__win32(bitValue); + iChannel += 1; + } } } } } + +#ifdef __cplusplus +#define mal_is_guid_equal(a, b) IsEqualGUID(*((const GUID*)a), *((const GUID*)b)) +#else +#define mal_is_guid_equal(a, b) IsEqualGUID((const GUID*)a, (const GUID*)b) +#endif + +mal_format mal_format_from_WAVEFORMATEX(WAVEFORMATEX* pWF) +{ + mal_assert(pWF != NULL); + + if (pWF->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + WAVEFORMATEXTENSIBLE* pWFEX = (WAVEFORMATEXTENSIBLE*)pWF; + if (mal_is_guid_equal(&pWFEX->SubFormat, &MAL_GUID_KSDATAFORMAT_SUBTYPE_PCM)) { + if (pWFEX->Samples.wValidBitsPerSample == 32) { + return mal_format_s32; + } + if (pWFEX->Samples.wValidBitsPerSample == 24) { + if (pWFEX->Format.wBitsPerSample == 32) { + //return mal_format_s24_32; + } + if (pWFEX->Format.wBitsPerSample == 24) { + return mal_format_s24; + } + } + if (pWFEX->Samples.wValidBitsPerSample == 16) { + return mal_format_s16; + } + if (pWFEX->Samples.wValidBitsPerSample == 8) { + return mal_format_u8; + } + } + if (mal_is_guid_equal(&pWFEX->SubFormat, &MAL_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { + if (pWFEX->Samples.wValidBitsPerSample == 32) { + return mal_format_f32; + } + //if (pWFEX->Samples.wValidBitsPerSample == 64) { + // return mal_format_f64; + //} + } + } else { + if (pWF->wFormatTag == WAVE_FORMAT_PCM) { + if (pWF->wBitsPerSample == 32) { + return mal_format_s32; + } + if (pWF->wBitsPerSample == 24) { + return mal_format_s24; + } + if (pWF->wBitsPerSample == 16) { + return mal_format_s16; + } + if (pWF->wBitsPerSample == 8) { + return mal_format_u8; + } + } + if (pWF->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { + if (pWF->wBitsPerSample == 32) { + return mal_format_f32; + } + if (pWF->wBitsPerSample == 64) { + //return mal_format_f64; + } + } + } + + return mal_format_unknown; +} #endif @@ -3223,234 +4376,519 @@ static void mal_channel_mask_to_channel_map__win32(DWORD dwChannelMask, mal_uint // /////////////////////////////////////////////////////////////////////////////// #ifdef MAL_HAS_WASAPI -#if defined(_MSC_VER) - #pragma warning(push) - #pragma warning(disable:4091) // 'typedef ': ignored on left of '' when no variable is declared -#endif -#include -#include -#include -#if defined(_MSC_VER) - #pragma warning(pop) +//#if defined(_MSC_VER) +// #pragma warning(push) +// #pragma warning(disable:4091) // 'typedef ': ignored on left of '' when no variable is declared +//#endif +//#include +//#include +//#if defined(_MSC_VER) +// #pragma warning(pop) +//#endif + +#ifndef PROPERTYKEY_DEFINED +#define PROPERTYKEY_DEFINED +typedef struct +{ + GUID fmtid; + DWORD pid; +} PROPERTYKEY; #endif -const PROPERTYKEY g_malPKEY_Device_FriendlyName = {{0xa45c254e, 0xdf1c, 0x4efd, {0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0}}, 14}; -const PROPERTYKEY g_malPKEY_AudioEngine_DeviceFormat = {{0xf19f064d, 0x82c, 0x4e27, {0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c}}, 0}; +// Some compilers don't define PropVariantInit(). We just do this ourselves since it's just a memset(). +static MAL_INLINE void mal_PropVariantInit(PROPVARIANT* pProp) +{ + mal_zero_object(pProp); +} -const IID g_malCLSID_MMDeviceEnumerator_Instance = {0xBCDE0395, 0xE52F, 0x467C, {0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E}}; // BCDE0395-E52F-467C-8E3D-C4579291692E = __uuidof(MMDeviceEnumerator) -const IID g_malIID_IMMDeviceEnumerator_Instance = {0xA95664D2, 0x9614, 0x4F35, {0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6}}; // A95664D2-9614-4F35-A746-DE8DB63617E6 = __uuidof(IMMDeviceEnumerator) -const IID g_malIID_IAudioClient_Instance = {0x1CB9AD4C, 0xDBFA, 0x4C32, {0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2}}; // 1CB9AD4C-DBFA-4C32-B178-C2F568A703B2 = __uuidof(IAudioClient) -const IID g_malIID_IAudioRenderClient_Instance = {0xF294ACFC, 0x3146, 0x4483, {0xA7, 0xBF, 0xAD, 0xDC, 0xA7, 0xC2, 0x60, 0xE2}}; // F294ACFC-3146-4483-A7BF-ADDCA7C260E2 = __uuidof(IAudioRenderClient) -const IID g_malIID_IAudioCaptureClient_Instance = {0xC8ADBD64, 0xE71E, 0x48A0, {0xA4, 0xDE, 0x18, 0x5C, 0x39, 0x5C, 0xD3, 0x17}}; // C8ADBD64-E71E-48A0-A4DE-185C395CD317 = __uuidof(IAudioCaptureClient) +const PROPERTYKEY MAL_PKEY_Device_FriendlyName = {{0xA45C254E, 0xDF1C, 0x4EFD, {0x80, 0x20, 0x67, 0xD1, 0x46, 0xA8, 0x50, 0xE0}}, 14}; +const PROPERTYKEY MAL_PKEY_AudioEngine_DeviceFormat = {{0xF19F064D, 0x82C, 0x4E27, {0xBC, 0x73, 0x68, 0x82, 0xA1, 0xBB, 0x8E, 0x4C}}, 0}; +const IID MAL_IID_IAudioClient = {0x1CB9AD4C, 0xDBFA, 0x4C32, {0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2}}; // 1CB9AD4C-DBFA-4C32-B178-C2F568A703B2 = __uuidof(IAudioClient) +const IID MAL_IID_IAudioRenderClient = {0xF294ACFC, 0x3146, 0x4483, {0xA7, 0xBF, 0xAD, 0xDC, 0xA7, 0xC2, 0x60, 0xE2}}; // F294ACFC-3146-4483-A7BF-ADDCA7C260E2 = __uuidof(IAudioRenderClient) +const IID MAL_IID_IAudioCaptureClient = {0xC8ADBD64, 0xE71E, 0x48A0, {0xA4, 0xDE, 0x18, 0x5C, 0x39, 0x5C, 0xD3, 0x17}}; // C8ADBD64-E71E-48A0-A4DE-185C395CD317 = __uuidof(IAudioCaptureClient) #ifndef MAL_WIN32_DESKTOP -const IID g_malIID_DEVINTERFACE_AUDIO_RENDER = {0xE6327CAD, 0xDCEC, 0x4949, {0xAE, 0x8A, 0x99, 0x1E, 0x97, 0x6A, 0x79, 0xD2}}; // E6327CAD-DCEC-4949-AE8A-991E976A79D2 -const IID g_malIID_DEVINTERFACE_AUDIO_CAPTURE = {0x2EEF81BE, 0x33FA, 0x4800, {0x96, 0x70, 0x1C, 0xD4, 0x74, 0x97, 0x2C, 0x3F}}; // 2EEF81BE-33FA-4800-9670-1CD474972C3F +const IID MAL_IID_DEVINTERFACE_AUDIO_RENDER = {0xE6327CAD, 0xDCEC, 0x4949, {0xAE, 0x8A, 0x99, 0x1E, 0x97, 0x6A, 0x79, 0xD2}}; // E6327CAD-DCEC-4949-AE8A-991E976A79D2 +const IID MAL_IID_DEVINTERFACE_AUDIO_CAPTURE = {0x2EEF81BE, 0x33FA, 0x4800, {0x96, 0x70, 0x1C, 0xD4, 0x74, 0x97, 0x2C, 0x3F}}; // 2EEF81BE-33FA-4800-9670-1CD474972C3F #endif +const IID MAL_CLSID_MMDeviceEnumerator_Instance = {0xBCDE0395, 0xE52F, 0x467C, {0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E}}; // BCDE0395-E52F-467C-8E3D-C4579291692E = __uuidof(MMDeviceEnumerator) +const IID MAL_IID_IMMDeviceEnumerator_Instance = {0xA95664D2, 0x9614, 0x4F35, {0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6}}; // A95664D2-9614-4F35-A746-DE8DB63617E6 = __uuidof(IMMDeviceEnumerator) #ifdef __cplusplus -#define g_malCLSID_MMDeviceEnumerator g_malCLSID_MMDeviceEnumerator_Instance -#define g_malIID_IMMDeviceEnumerator g_malIID_IMMDeviceEnumerator_Instance -#define g_malIID_IAudioClient g_malIID_IAudioClient_Instance -#define g_malIID_IAudioRenderClient g_malIID_IAudioRenderClient_Instance -#define g_malIID_IAudioCaptureClient g_malIID_IAudioCaptureClient_Instance +#define MAL_CLSID_MMDeviceEnumerator MAL_CLSID_MMDeviceEnumerator_Instance +#define MAL_IID_IMMDeviceEnumerator MAL_IID_IMMDeviceEnumerator_Instance #else -#define g_malCLSID_MMDeviceEnumerator &g_malCLSID_MMDeviceEnumerator_Instance -#define g_malIID_IMMDeviceEnumerator &g_malIID_IMMDeviceEnumerator_Instance -#define g_malIID_IAudioClient &g_malIID_IAudioClient_Instance -#define g_malIID_IAudioRenderClient &g_malIID_IAudioRenderClient_Instance -#define g_malIID_IAudioCaptureClient &g_malIID_IAudioCaptureClient_Instance -#endif - -#ifdef __cplusplus -#define mal_is_guid_equal(a, b) IsEqualGUID(a, b) -#else -#define mal_is_guid_equal(a, b) IsEqualGUID(&a, &b) +#define MAL_CLSID_MMDeviceEnumerator &MAL_CLSID_MMDeviceEnumerator_Instance +#define MAL_IID_IMMDeviceEnumerator &MAL_IID_IMMDeviceEnumerator_Instance #endif +#ifdef MAL_WIN32_DESKTOP +#define MAL_MM_DEVICE_STATE_ACTIVE 1 +#define MAL_MM_DEVICE_STATE_DISABLED 2 +#define MAL_MM_DEVICE_STATE_NOTPRESENT 4 +#define MAL_MM_DEVICE_STATE_UNPLUGGED 8 + +typedef struct mal_IMMDeviceEnumerator mal_IMMDeviceEnumerator; +typedef struct mal_IMMDeviceCollection mal_IMMDeviceCollection; +typedef struct mal_IMMDevice mal_IMMDevice; +typedef struct mal_IMMNotificationClient mal_IMMNotificationClient; +#else +typedef struct mal_IActivateAudioInterfaceAsyncOperation mal_IActivateAudioInterfaceAsyncOperation; +#endif +typedef struct mal_IPropertyStore mal_IPropertyStore; +typedef struct mal_IAudioClient mal_IAudioClient; +typedef struct mal_IAudioRenderClient mal_IAudioRenderClient; +typedef struct mal_IAudioCaptureClient mal_IAudioCaptureClient; + +typedef mal_int64 MAL_REFERENCE_TIME; + +#define MAL_AUDCLNT_STREAMFLAGS_CROSSPROCESS 0x00010000 +#define MAL_AUDCLNT_STREAMFLAGS_LOOPBACK 0x00020000 +#define MAL_AUDCLNT_STREAMFLAGS_EVENTCALLBACK 0x00040000 +#define MAL_AUDCLNT_STREAMFLAGS_NOPERSIST 0x00080000 +#define MAL_AUDCLNT_STREAMFLAGS_RATEADJUST 0x00100000 +#define MAL_AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000 +#define MAL_AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000 +#define MAL_AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED 0x10000000 +#define MAL_AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE 0x20000000 +#define MAL_AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED 0x40000000 + +// We only care about a few error codes. +#define MAL_AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED (-2004287463) +#define MAL_AUDCLNT_S_BUFFER_EMPTY (143196161) + +typedef enum +{ + mal_eRender = 0, + mal_eCapture = 1, + mal_eAll = 2 +} mal_EDataFlow; + +typedef enum +{ + mal_eConsole = 0, + mal_eMultimedia = 1, + mal_eCommunications = 2 +} mal_ERole; + +typedef enum +{ + MAL_AUDCLNT_SHAREMODE_SHARED, + MAL_AUDCLNT_SHAREMODE_EXCLUSIVE +} MAL_AUDCLNT_SHAREMODE; + #ifdef MAL_WIN32_DESKTOP // IMMDeviceEnumerator - #ifdef __cplusplus - #define IMMDeviceEnumerator_Release(p) ((IMMDeviceEnumerator*)p)->Release() - #else - #define IMMDeviceEnumerator_Release(p) ((IMMDeviceEnumerator*)p)->lpVtbl->Release((IMMDeviceEnumerator*)p) - #endif - #ifdef __cplusplus - #define IMMDeviceEnumerator_EnumAudioEndpoints(p, a, b, c) ((IMMDeviceEnumerator*)p)->EnumAudioEndpoints(a, b, c) - #else - #define IMMDeviceEnumerator_EnumAudioEndpoints(p, a, b, c) ((IMMDeviceEnumerator*)p)->lpVtbl->EnumAudioEndpoints(p, a, b, c) - #endif - #ifdef __cplusplus - #define IMMDeviceEnumerator_GetDefaultAudioEndpoint(p, a, b, c) ((IMMDeviceEnumerator*)p)->GetDefaultAudioEndpoint(a, b, c) - #else - #define IMMDeviceEnumerator_GetDefaultAudioEndpoint(p, a, b, c) ((IMMDeviceEnumerator*)p)->lpVtbl->GetDefaultAudioEndpoint(p, a, b, c) - #endif - #ifdef __cplusplus - #define IMMDeviceEnumerator_GetDevice(p, a, b) ((IMMDeviceEnumerator*)p)->GetDevice(a, b) - #else - #define IMMDeviceEnumerator_GetDevice(p, a, b) ((IMMDeviceEnumerator*)p)->lpVtbl->GetDevice(p, a, b) - #endif + typedef struct + { + // IUnknown + HRESULT (STDMETHODCALLTYPE * QueryInterface)(mal_IMMDeviceEnumerator* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (mal_IMMDeviceEnumerator* pThis); + ULONG (STDMETHODCALLTYPE * Release) (mal_IMMDeviceEnumerator* pThis); + + // IMMDeviceEnumerator + HRESULT (STDMETHODCALLTYPE * EnumAudioEndpoints) (mal_IMMDeviceEnumerator* pThis, mal_EDataFlow dataFlow, DWORD dwStateMask, mal_IMMDeviceCollection** ppDevices); + HRESULT (STDMETHODCALLTYPE * GetDefaultAudioEndpoint) (mal_IMMDeviceEnumerator* pThis, mal_EDataFlow dataFlow, mal_ERole role, mal_IMMDevice** ppEndpoint); + HRESULT (STDMETHODCALLTYPE * GetDevice) (mal_IMMDeviceEnumerator* pThis, LPCWSTR pID, mal_IMMDevice** ppDevice); + HRESULT (STDMETHODCALLTYPE * RegisterEndpointNotificationCallback) (mal_IMMDeviceEnumerator* pThis, mal_IMMNotificationClient* pClient); + HRESULT (STDMETHODCALLTYPE * UnregisterEndpointNotificationCallback)(mal_IMMDeviceEnumerator* pThis, mal_IMMNotificationClient* pClient); + } mal_IMMDeviceEnumeratorVtbl; + struct mal_IMMDeviceEnumerator + { + mal_IMMDeviceEnumeratorVtbl* lpVtbl; + }; + HRESULT mal_IMMDeviceEnumerator_QueryInterface(mal_IMMDeviceEnumerator* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } + ULONG mal_IMMDeviceEnumerator_AddRef(mal_IMMDeviceEnumerator* pThis) { return pThis->lpVtbl->AddRef(pThis); } + ULONG mal_IMMDeviceEnumerator_Release(mal_IMMDeviceEnumerator* pThis) { return pThis->lpVtbl->Release(pThis); } + HRESULT mal_IMMDeviceEnumerator_EnumAudioEndpoints(mal_IMMDeviceEnumerator* pThis, mal_EDataFlow dataFlow, DWORD dwStateMask, mal_IMMDeviceCollection** ppDevices) { return pThis->lpVtbl->EnumAudioEndpoints(pThis, dataFlow, dwStateMask, ppDevices); } + HRESULT mal_IMMDeviceEnumerator_GetDefaultAudioEndpoint(mal_IMMDeviceEnumerator* pThis, mal_EDataFlow dataFlow, mal_ERole role, mal_IMMDevice** ppEndpoint) { return pThis->lpVtbl->GetDefaultAudioEndpoint(pThis, dataFlow, role, ppEndpoint); } + HRESULT mal_IMMDeviceEnumerator_GetDevice(mal_IMMDeviceEnumerator* pThis, LPCWSTR pID, mal_IMMDevice** ppDevice) { return pThis->lpVtbl->GetDevice(pThis, pID, ppDevice); } + HRESULT mal_IMMDeviceEnumerator_RegisterEndpointNotificationCallback(mal_IMMDeviceEnumerator* pThis, mal_IMMNotificationClient* pClient) { return pThis->lpVtbl->RegisterEndpointNotificationCallback(pThis, pClient); } + HRESULT mal_IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(mal_IMMDeviceEnumerator* pThis, mal_IMMNotificationClient* pClient) { return pThis->lpVtbl->UnregisterEndpointNotificationCallback(pThis, pClient); } + // IMMDeviceCollection - #ifdef __cplusplus - #define IMMDeviceCollection_Release(p) ((IMMDeviceCollection*)p)->Release() - #else - #define IMMDeviceCollection_Release(p) ((IMMDeviceCollection*)p)->lpVtbl->Release((IMMDeviceCollection*)p) - #endif - #ifdef __cplusplus - #define IMMDeviceCollection_GetCount(p, a) ((IMMDeviceCollection*)p)->GetCount(a) - #else - #define IMMDeviceCollection_GetCount(p, a) ((IMMDeviceCollection*)p)->lpVtbl->GetCount((IMMDeviceCollection*)p, a) - #endif - #ifdef __cplusplus - #define IMMDeviceCollection_Item(p, a, b) ((IMMDeviceCollection*)p)->Item(a, b) - #else - #define IMMDeviceCollection_Item(p, a, b) ((IMMDeviceCollection*)p)->lpVtbl->Item((IMMDeviceCollection*)p, a, b) - #endif + typedef struct + { + // IUnknown + HRESULT (STDMETHODCALLTYPE * QueryInterface)(mal_IMMDeviceCollection* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (mal_IMMDeviceCollection* pThis); + ULONG (STDMETHODCALLTYPE * Release) (mal_IMMDeviceCollection* pThis); + + // IMMDeviceCollection + HRESULT (STDMETHODCALLTYPE * GetCount)(mal_IMMDeviceCollection* pThis, UINT* pDevices); + HRESULT (STDMETHODCALLTYPE * Item) (mal_IMMDeviceCollection* pThis, UINT nDevice, mal_IMMDevice** ppDevice); + } mal_IMMDeviceCollectionVtbl; + struct mal_IMMDeviceCollection + { + mal_IMMDeviceCollectionVtbl* lpVtbl; + }; + HRESULT mal_IMMDeviceCollection_QueryInterface(mal_IMMDeviceCollection* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } + ULONG mal_IMMDeviceCollection_AddRef(mal_IMMDeviceCollection* pThis) { return pThis->lpVtbl->AddRef(pThis); } + ULONG mal_IMMDeviceCollection_Release(mal_IMMDeviceCollection* pThis) { return pThis->lpVtbl->Release(pThis); } + HRESULT mal_IMMDeviceCollection_GetCount(mal_IMMDeviceCollection* pThis, UINT* pDevices) { return pThis->lpVtbl->GetCount(pThis, pDevices); } + HRESULT mal_IMMDeviceCollection_Item(mal_IMMDeviceCollection* pThis, UINT nDevice, mal_IMMDevice** ppDevice) { return pThis->lpVtbl->Item(pThis, nDevice, ppDevice); } + // IMMDevice - #ifdef __cplusplus - #define IMMDevice_Release(p) ((IMMDevice*)p)->Release() - #else - #define IMMDevice_Release(p) ((IMMDevice*)p)->lpVtbl->Release((IMMDevice*)p) - #endif - #ifdef __cplusplus - #define IMMDevice_GetId(p, a) ((IMMDevice*)p)->GetId(a) - #else - #define IMMDevice_GetId(p, a) ((IMMDevice*)p)->lpVtbl->GetId((IMMDevice*)p, a) - #endif - #ifdef __cplusplus - #define IMMDevice_OpenPropertyStore(p, a, b) ((IMMDevice*)p)->OpenPropertyStore(a, b) - #else - #define IMMDevice_OpenPropertyStore(p, a, b) ((IMMDevice*)p)->lpVtbl->OpenPropertyStore((IMMDevice*)p, a, b) - #endif - #ifdef __cplusplus - #define IMMDevice_Activate(p, a, b, c, d) ((IMMDevice*)p)->Activate(a, b, c, d) - #else - #define IMMDevice_Activate(p, a, b, c, d) ((IMMDevice*)p)->lpVtbl->Activate((IMMDevice*)p, a, b, c, d) - #endif + typedef struct + { + // IUnknown + HRESULT (STDMETHODCALLTYPE * QueryInterface)(mal_IMMDevice* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (mal_IMMDevice* pThis); + ULONG (STDMETHODCALLTYPE * Release) (mal_IMMDevice* pThis); + + // IMMDevice + HRESULT (STDMETHODCALLTYPE * Activate) (mal_IMMDevice* pThis, const IID* const iid, DWORD dwClsCtx, PROPVARIANT* pActivationParams, void** ppInterface); + HRESULT (STDMETHODCALLTYPE * OpenPropertyStore)(mal_IMMDevice* pThis, DWORD stgmAccess, mal_IPropertyStore** ppProperties); + HRESULT (STDMETHODCALLTYPE * GetId) (mal_IMMDevice* pThis, LPWSTR *pID); + HRESULT (STDMETHODCALLTYPE * GetState) (mal_IMMDevice* pThis, DWORD *pState); + } mal_IMMDeviceVtbl; + struct mal_IMMDevice + { + mal_IMMDeviceVtbl* lpVtbl; + }; + HRESULT mal_IMMDevice_QueryInterface(mal_IMMDevice* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } + ULONG mal_IMMDevice_AddRef(mal_IMMDevice* pThis) { return pThis->lpVtbl->AddRef(pThis); } + ULONG mal_IMMDevice_Release(mal_IMMDevice* pThis) { return pThis->lpVtbl->Release(pThis); } + HRESULT mal_IMMDevice_Activate(mal_IMMDevice* pThis, const IID* const iid, DWORD dwClsCtx, PROPVARIANT* pActivationParams, void** ppInterface) { return pThis->lpVtbl->Activate(pThis, iid, dwClsCtx, pActivationParams, ppInterface); } + HRESULT mal_IMMDevice_OpenPropertyStore(mal_IMMDevice* pThis, DWORD stgmAccess, mal_IPropertyStore** ppProperties) { return pThis->lpVtbl->OpenPropertyStore(pThis, stgmAccess, ppProperties); } + HRESULT mal_IMMDevice_GetId(mal_IMMDevice* pThis, LPWSTR *pID) { return pThis->lpVtbl->GetId(pThis, pID); } + HRESULT mal_IMMDevice_GetState(mal_IMMDevice* pThis, DWORD *pState) { return pThis->lpVtbl->GetState(pThis, pState); } #else + // IActivateAudioInterfaceAsyncOperation - #ifdef __cplusplus - #define IActivateAudioInterfaceAsyncOperation_Release(p) ((IActivateAudioInterfaceAsyncOperation*)p)->Release() - #else - #define IActivateAudioInterfaceAsyncOperation_Release(p) ((IActivateAudioInterfaceAsyncOperation*)p)->lpVtbl->Release((IActivateAudioInterfaceAsyncOperation*)p) - #endif - #ifdef __cplusplus - #define IActivateAudioInterfaceAsyncOperation_GetActivateResult(p, a, b) ((IActivateAudioInterfaceAsyncOperation*)p)->GetActivateResult(a, b) - #else - #define IActivateAudioInterfaceAsyncOperation_GetActivateResult(p, a, b) ((IActivateAudioInterfaceAsyncOperation*)p)->lpVtbl->GetActivateResult((IActivateAudioInterfaceAsyncOperation*)p, a, b) - #endif + typedef struct + { + // IUnknown + HRESULT (STDMETHODCALLTYPE * QueryInterface)(mal_IActivateAudioInterfaceAsyncOperation* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (mal_IActivateAudioInterfaceAsyncOperation* pThis); + ULONG (STDMETHODCALLTYPE * Release) (mal_IActivateAudioInterfaceAsyncOperation* pThis); + + // IActivateAudioInterfaceAsyncOperation + HRESULT (STDMETHODCALLTYPE * GetActivateResult)(mal_IActivateAudioInterfaceAsyncOperation* pThis, HRESULT *pActivateResult, IUnknown** ppActivatedInterface); + } mal_IActivateAudioInterfaceAsyncOperationVtbl; + struct mal_IActivateAudioInterfaceAsyncOperation + { + mal_IActivateAudioInterfaceAsyncOperationVtbl* lpVtbl; + }; + HRESULT mal_IActivateAudioInterfaceAsyncOperation_QueryInterface(mal_IActivateAudioInterfaceAsyncOperation* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } + ULONG mal_IActivateAudioInterfaceAsyncOperation_AddRef(mal_IActivateAudioInterfaceAsyncOperation* pThis) { return pThis->lpVtbl->AddRef(pThis); } + ULONG mal_IActivateAudioInterfaceAsyncOperation_Release(mal_IActivateAudioInterfaceAsyncOperation* pThis) { return pThis->lpVtbl->Release(pThis); } + HRESULT mal_IActivateAudioInterfaceAsyncOperation_GetActivateResult(mal_IActivateAudioInterfaceAsyncOperation* pThis, HRESULT *pActivateResult, IUnknown** ppActivatedInterface) { return pThis->lpVtbl->GetActivateResult(pThis, pActivateResult, ppActivatedInterface); } #endif // IPropertyStore -#ifdef __cplusplus - #define IPropertyStore_Release(p) ((IPropertyStore*)p)->Release() -#else - #define IPropertyStore_Release(p) ((IPropertyStore*)p)->lpVtbl->Release((IPropertyStore*)p) -#endif -#ifdef __cplusplus - #define IPropertyStore_GetValue(p, a, b) ((IPropertyStore*)p)->GetValue(a, b) -#else - #define IPropertyStore_GetValue(p, a, b) ((IPropertyStore*)p)->lpVtbl->GetValue((IPropertyStore*)p, &a, b) -#endif +typedef struct +{ + // IUnknown + HRESULT (STDMETHODCALLTYPE * QueryInterface)(mal_IPropertyStore* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (mal_IPropertyStore* pThis); + ULONG (STDMETHODCALLTYPE * Release) (mal_IPropertyStore* pThis); + + // IPropertyStore + HRESULT (STDMETHODCALLTYPE * GetCount)(mal_IPropertyStore* pThis, DWORD* pPropCount); + HRESULT (STDMETHODCALLTYPE * GetAt) (mal_IPropertyStore* pThis, DWORD propIndex, PROPERTYKEY* pPropKey); + HRESULT (STDMETHODCALLTYPE * GetValue)(mal_IPropertyStore* pThis, const PROPERTYKEY* const pKey, PROPVARIANT* pPropVar); + HRESULT (STDMETHODCALLTYPE * SetValue)(mal_IPropertyStore* pThis, const PROPERTYKEY* const pKey, const PROPVARIANT* const pPropVar); + HRESULT (STDMETHODCALLTYPE * Commit) (mal_IPropertyStore* pThis); +} mal_IPropertyStoreVtbl; +struct mal_IPropertyStore +{ + mal_IPropertyStoreVtbl* lpVtbl; +}; +HRESULT mal_IPropertyStore_QueryInterface(mal_IPropertyStore* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG mal_IPropertyStore_AddRef(mal_IPropertyStore* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG mal_IPropertyStore_Release(mal_IPropertyStore* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT mal_IPropertyStore_GetCount(mal_IPropertyStore* pThis, DWORD* pPropCount) { return pThis->lpVtbl->GetCount(pThis, pPropCount); } +HRESULT mal_IPropertyStore_GetAt(mal_IPropertyStore* pThis, DWORD propIndex, PROPERTYKEY* pPropKey) { return pThis->lpVtbl->GetAt(pThis, propIndex, pPropKey); } +HRESULT mal_IPropertyStore_GetValue(mal_IPropertyStore* pThis, const PROPERTYKEY* const pKey, PROPVARIANT* pPropVar) { return pThis->lpVtbl->GetValue(pThis, pKey, pPropVar); } +HRESULT mal_IPropertyStore_SetValue(mal_IPropertyStore* pThis, const PROPERTYKEY* const pKey, const PROPVARIANT* const pPropVar) { return pThis->lpVtbl->SetValue(pThis, pKey, pPropVar); } +HRESULT mal_IPropertyStore_Commit(mal_IPropertyStore* pThis) { return pThis->lpVtbl->Commit(pThis); } + // IAudioClient -#ifdef __cplusplus - #define IAudioClient_Release(p) ((IAudioClient*)p)->Release() -#else - #define IAudioClient_Release(p) ((IAudioClient*)p)->lpVtbl->Release((IAudioClient*)p) -#endif -#ifdef __cplusplus - #define IAudioClient_IsFormatSupported(p, a, b, c) ((IAudioClient*)p)->IsFormatSupported(a, b, c) -#else - #define IAudioClient_IsFormatSupported(p, a, b, c) ((IAudioClient*)p)->lpVtbl->IsFormatSupported((IAudioClient*)p, a, b, c) -#endif -#ifdef __cplusplus - #define IAudioClient_GetMixFormat(p, a) ((IAudioClient*)p)->GetMixFormat(a) -#else - #define IAudioClient_GetMixFormat(p, a) ((IAudioClient*)p)->lpVtbl->GetMixFormat((IAudioClient*)p, a) -#endif -#ifdef __cplusplus - #define IAudioClient_Initialize(p, a, b, c, d, e, f) ((IAudioClient*)p)->Initialize(a, b, c, d, e, f) -#else - #define IAudioClient_Initialize(p, a, b, c, d, e, f) ((IAudioClient*)p)->lpVtbl->Initialize((IAudioClient*)p, a, b, c, d, e, f) -#endif -#ifdef __cplusplus - #define IAudioClient_GetBufferSize(p, a) ((IAudioClient*)p)->GetBufferSize(a) -#else - #define IAudioClient_GetBufferSize(p, a) ((IAudioClient*)p)->lpVtbl->GetBufferSize((IAudioClient*)p, a) -#endif -#ifdef __cplusplus - #define IAudioClient_GetService(p, a, b) ((IAudioClient*)p)->GetService(a, b) -#else - #define IAudioClient_GetService(p, a, b) ((IAudioClient*)p)->lpVtbl->GetService((IAudioClient*)p, a, b) -#endif -#ifdef __cplusplus - #define IAudioClient_Start(p) ((IAudioClient*)p)->Start() -#else - #define IAudioClient_Start(p) ((IAudioClient*)p)->lpVtbl->Start((IAudioClient*)p) -#endif -#ifdef __cplusplus - #define IAudioClient_Stop(p) ((IAudioClient*)p)->Stop() -#else - #define IAudioClient_Stop(p) ((IAudioClient*)p)->lpVtbl->Stop((IAudioClient*)p) -#endif -#ifdef __cplusplus - #define IAudioClient_GetCurrentPadding(p, a) ((IAudioClient*)p)->GetCurrentPadding(a) -#else - #define IAudioClient_GetCurrentPadding(p, a) ((IAudioClient*)p)->lpVtbl->GetCurrentPadding((IAudioClient*)p, a) -#endif -#ifdef __cplusplus - #define IAudioClient_SetEventHandle(p, a) ((IAudioClient*)p)->SetEventHandle(a) -#else - #define IAudioClient_SetEventHandle(p, a) ((IAudioClient*)p)->lpVtbl->SetEventHandle((IAudioClient*)p, a) -#endif +typedef struct +{ + // IUnknown + HRESULT (STDMETHODCALLTYPE * QueryInterface)(mal_IAudioClient* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (mal_IAudioClient* pThis); + ULONG (STDMETHODCALLTYPE * Release) (mal_IAudioClient* pThis); + + // IAudioClient + HRESULT (STDMETHODCALLTYPE * Initialize) (mal_IAudioClient* pThis, MAL_AUDCLNT_SHAREMODE shareMode, DWORD streamFlags, MAL_REFERENCE_TIME bufferDuration, MAL_REFERENCE_TIME periodicity, const WAVEFORMATEX* pFormat, const GUID* pAudioSessionGuid); + HRESULT (STDMETHODCALLTYPE * GetBufferSize) (mal_IAudioClient* pThis, UINT32* pNumBufferFrames); + HRESULT (STDMETHODCALLTYPE * GetStreamLatency) (mal_IAudioClient* pThis, MAL_REFERENCE_TIME* pLatency); + HRESULT (STDMETHODCALLTYPE * GetCurrentPadding)(mal_IAudioClient* pThis, UINT32* pNumPaddingFrames); + HRESULT (STDMETHODCALLTYPE * IsFormatSupported)(mal_IAudioClient* pThis, MAL_AUDCLNT_SHAREMODE shareMode, const WAVEFORMATEX* pFormat, WAVEFORMATEX** ppClosestMatch); + HRESULT (STDMETHODCALLTYPE * GetMixFormat) (mal_IAudioClient* pThis, WAVEFORMATEX** ppDeviceFormat); + HRESULT (STDMETHODCALLTYPE * GetDevicePeriod) (mal_IAudioClient* pThis, MAL_REFERENCE_TIME* pDefaultDevicePeriod, MAL_REFERENCE_TIME* pMinimumDevicePeriod); + HRESULT (STDMETHODCALLTYPE * Start) (mal_IAudioClient* pThis); + HRESULT (STDMETHODCALLTYPE * Stop) (mal_IAudioClient* pThis); + HRESULT (STDMETHODCALLTYPE * Reset) (mal_IAudioClient* pThis); + HRESULT (STDMETHODCALLTYPE * SetEventHandle) (mal_IAudioClient* pThis, HANDLE eventHandle); + HRESULT (STDMETHODCALLTYPE * GetService) (mal_IAudioClient* pThis, const IID* const riid, void** pp); +} mal_IAudioClientVtbl; +struct mal_IAudioClient +{ + mal_IAudioClientVtbl* lpVtbl; +}; +HRESULT mal_IAudioClient_QueryInterface(mal_IAudioClient* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG mal_IAudioClient_AddRef(mal_IAudioClient* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG mal_IAudioClient_Release(mal_IAudioClient* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT mal_IAudioClient_Initialize(mal_IAudioClient* pThis, MAL_AUDCLNT_SHAREMODE shareMode, DWORD streamFlags, MAL_REFERENCE_TIME bufferDuration, MAL_REFERENCE_TIME periodicity, const WAVEFORMATEX* pFormat, const GUID* pAudioSessionGuid) { return pThis->lpVtbl->Initialize(pThis, shareMode, streamFlags, bufferDuration, periodicity, pFormat, pAudioSessionGuid); } +HRESULT mal_IAudioClient_GetBufferSize(mal_IAudioClient* pThis, UINT32* pNumBufferFrames) { return pThis->lpVtbl->GetBufferSize(pThis, pNumBufferFrames); } +HRESULT mal_IAudioClient_GetStreamLatency(mal_IAudioClient* pThis, MAL_REFERENCE_TIME* pLatency) { return pThis->lpVtbl->GetStreamLatency(pThis, pLatency); } +HRESULT mal_IAudioClient_GetCurrentPadding(mal_IAudioClient* pThis, UINT32* pNumPaddingFrames) { return pThis->lpVtbl->GetCurrentPadding(pThis, pNumPaddingFrames); } +HRESULT mal_IAudioClient_IsFormatSupported(mal_IAudioClient* pThis, MAL_AUDCLNT_SHAREMODE shareMode, const WAVEFORMATEX* pFormat, WAVEFORMATEX** ppClosestMatch) { return pThis->lpVtbl->IsFormatSupported(pThis, shareMode, pFormat, ppClosestMatch); } +HRESULT mal_IAudioClient_GetMixFormat(mal_IAudioClient* pThis, WAVEFORMATEX** ppDeviceFormat) { return pThis->lpVtbl->GetMixFormat(pThis, ppDeviceFormat); } +HRESULT mal_IAudioClient_GetDevicePeriod(mal_IAudioClient* pThis, MAL_REFERENCE_TIME* pDefaultDevicePeriod, MAL_REFERENCE_TIME* pMinimumDevicePeriod) { return pThis->lpVtbl->GetDevicePeriod(pThis, pDefaultDevicePeriod, pMinimumDevicePeriod); } +HRESULT mal_IAudioClient_Start(mal_IAudioClient* pThis) { return pThis->lpVtbl->Start(pThis); } +HRESULT mal_IAudioClient_Stop(mal_IAudioClient* pThis) { return pThis->lpVtbl->Stop(pThis); } +HRESULT mal_IAudioClient_Reset(mal_IAudioClient* pThis) { return pThis->lpVtbl->Reset(pThis); } +HRESULT mal_IAudioClient_SetEventHandle(mal_IAudioClient* pThis, HANDLE eventHandle) { return pThis->lpVtbl->SetEventHandle(pThis, eventHandle); } +HRESULT mal_IAudioClient_GetService(mal_IAudioClient* pThis, const IID* const riid, void** pp) { return pThis->lpVtbl->GetService(pThis, riid, pp); } + // IAudioRenderClient -#ifdef __cplusplus - #define IAudioRenderClient_Release(p) ((IAudioRenderClient*)p)->Release() -#else - #define IAudioRenderClient_Release(p) ((IAudioRenderClient*)p)->lpVtbl->Release((IAudioRenderClient*)p) -#endif -#ifdef __cplusplus - #define IAudioRenderClient_GetBuffer(p, a, b) ((IAudioRenderClient*)p)->GetBuffer(a, b) -#else - #define IAudioRenderClient_GetBuffer(p, a, b) ((IAudioRenderClient*)p)->lpVtbl->GetBuffer((IAudioRenderClient*)p, a, b) -#endif -#ifdef __cplusplus - #define IAudioRenderClient_ReleaseBuffer(p, a, b) ((IAudioRenderClient*)p)->ReleaseBuffer(a, b) -#else - #define IAudioRenderClient_ReleaseBuffer(p, a, b) ((IAudioRenderClient*)p)->lpVtbl->ReleaseBuffer((IAudioRenderClient*)p, a, b) -#endif +typedef struct +{ + // IUnknown + HRESULT (STDMETHODCALLTYPE * QueryInterface)(mal_IAudioRenderClient* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (mal_IAudioRenderClient* pThis); + ULONG (STDMETHODCALLTYPE * Release) (mal_IAudioRenderClient* pThis); + + // IAudioRenderClient + HRESULT (STDMETHODCALLTYPE * GetBuffer) (mal_IAudioRenderClient* pThis, UINT32 numFramesRequested, BYTE** ppData); + HRESULT (STDMETHODCALLTYPE * ReleaseBuffer)(mal_IAudioRenderClient* pThis, UINT32 numFramesWritten, DWORD dwFlags); +} mal_IAudioRenderClientVtbl; +struct mal_IAudioRenderClient +{ + mal_IAudioRenderClientVtbl* lpVtbl; +}; +HRESULT mal_IAudioRenderClient_QueryInterface(mal_IAudioRenderClient* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG mal_IAudioRenderClient_AddRef(mal_IAudioRenderClient* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG mal_IAudioRenderClient_Release(mal_IAudioRenderClient* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT mal_IAudioRenderClient_GetBuffer(mal_IAudioRenderClient* pThis, UINT32 numFramesRequested, BYTE** ppData) { return pThis->lpVtbl->GetBuffer(pThis, numFramesRequested, ppData); } +HRESULT mal_IAudioRenderClient_ReleaseBuffer(mal_IAudioRenderClient* pThis, UINT32 numFramesWritten, DWORD dwFlags) { return pThis->lpVtbl->ReleaseBuffer(pThis, numFramesWritten, dwFlags); } + // IAudioCaptureClient -#ifdef __cplusplus - #define IAudioCaptureClient_Release(p) ((IAudioCaptureClient*)p)->Release() +typedef struct +{ + // IUnknown + HRESULT (STDMETHODCALLTYPE * QueryInterface)(mal_IAudioCaptureClient* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (mal_IAudioCaptureClient* pThis); + ULONG (STDMETHODCALLTYPE * Release) (mal_IAudioCaptureClient* pThis); + + // IAudioRenderClient + HRESULT (STDMETHODCALLTYPE * GetBuffer) (mal_IAudioCaptureClient* pThis, BYTE** ppData, UINT32* pNumFramesToRead, DWORD* pFlags, UINT64* pDevicePosition, UINT64* pQPCPosition); + HRESULT (STDMETHODCALLTYPE * ReleaseBuffer) (mal_IAudioCaptureClient* pThis, UINT32 numFramesRead); + HRESULT (STDMETHODCALLTYPE * GetNextPacketSize)(mal_IAudioCaptureClient* pThis, UINT32* pNumFramesInNextPacket); +} mal_IAudioCaptureClientVtbl; +struct mal_IAudioCaptureClient +{ + mal_IAudioCaptureClientVtbl* lpVtbl; +}; +HRESULT mal_IAudioCaptureClient_QueryInterface(mal_IAudioCaptureClient* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG mal_IAudioCaptureClient_AddRef(mal_IAudioCaptureClient* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG mal_IAudioCaptureClient_Release(mal_IAudioCaptureClient* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT mal_IAudioCaptureClient_GetBuffer(mal_IAudioCaptureClient* pThis, BYTE** ppData, UINT32* pNumFramesToRead, DWORD* pFlags, UINT64* pDevicePosition, UINT64* pQPCPosition) { return pThis->lpVtbl->GetBuffer(pThis, ppData, pNumFramesToRead, pFlags, pDevicePosition, pQPCPosition); } +HRESULT mal_IAudioCaptureClient_ReleaseBuffer(mal_IAudioCaptureClient* pThis, UINT32 numFramesRead) { return pThis->lpVtbl->ReleaseBuffer(pThis, numFramesRead); } +HRESULT mal_IAudioCaptureClient_GetNextPacketSize(mal_IAudioCaptureClient* pThis, UINT32* pNumFramesInNextPacket) { return pThis->lpVtbl->GetNextPacketSize(pThis, pNumFramesInNextPacket); } + +mal_bool32 mal_context_is_device_id_equal__wasapi(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) +{ + mal_assert(pContext != NULL); + mal_assert(pID0 != NULL); + mal_assert(pID1 != NULL); + (void)pContext; + + return memcmp(pID0->wasapi, pID1->wasapi, sizeof(pID0->wasapi)) == 0; +} + +mal_result mal_context_get_device_info_from_MMDevice__wasapi(mal_context* pContext, mal_IMMDevice* pMMDevice, mal_share_mode shareMode, mal_bool32 onlySimpleInfo, mal_device_info* pInfo) +{ + mal_assert(pContext != NULL); + mal_assert(pMMDevice != NULL); + mal_assert(pInfo != NULL); + + // ID. + LPWSTR id; + HRESULT hr = mal_IMMDevice_GetId(pMMDevice, &id); + if (SUCCEEDED(hr)) { + size_t idlen = wcslen(id); + if (idlen+1 > mal_countof(pInfo->id.wasapi)) { + mal_CoTaskMemFree(pContext, id); + mal_assert(MAL_FALSE); // NOTE: If this is triggered, please report it. It means the format of the ID must haved change and is too long to fit in our fixed sized buffer. + return MAL_ERROR; + } + + mal_copy_memory(pInfo->id.wasapi, id, idlen * sizeof(wchar_t)); + pInfo->id.wasapi[idlen] = '\0'; + + mal_CoTaskMemFree(pContext, id); + } + + mal_IPropertyStore *pProperties; + hr = mal_IMMDevice_OpenPropertyStore(pMMDevice, STGM_READ, &pProperties); + if (SUCCEEDED(hr)) { + PROPVARIANT var; + + // Description / Friendly Name + mal_PropVariantInit(&var); + hr = mal_IPropertyStore_GetValue(pProperties, &MAL_PKEY_Device_FriendlyName, &var); + if (SUCCEEDED(hr)) { + WideCharToMultiByte(CP_UTF8, 0, var.pwszVal, -1, pInfo->name, sizeof(pInfo->name), 0, FALSE); + mal_PropVariantClear(pContext, &var); + } + + // Format + if (!onlySimpleInfo) { + // TODO: + // - Get MAL_PKEY_AudioEngine_DeviceFormat for the channel count + // - Open the device + // - If shared mode, call GetMixFormat() + // - If exclusive mode, loop over most common formats (s16, s24, f32), then each of the standard sample rates. + // - If anything fails, don't allow exclusive mode for this device. + (void)shareMode; + } + + mal_IPropertyStore_Release(pProperties); + } + + return MAL_SUCCESS; +} + +mal_result mal_context_enumerate_device_collection__wasapi(mal_context* pContext, mal_IMMDeviceCollection* pDeviceCollection, mal_device_type deviceType, mal_enum_devices_callback_proc callback, void* pUserData) +{ + mal_assert(pContext != NULL); + mal_assert(callback != NULL); + + UINT deviceCount; + HRESULT hr = mal_IMMDeviceCollection_GetCount(pDeviceCollection, &deviceCount); + if (FAILED(hr)) { + return mal_context_post_error(pContext, NULL, "[WASAPI] Failed to get playback device count.", MAL_NO_DEVICE); + } + + for (mal_uint32 iDevice = 0; iDevice < deviceCount; ++iDevice) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + + mal_IMMDevice* pMMDevice; + hr = mal_IMMDeviceCollection_Item(pDeviceCollection, iDevice, &pMMDevice); + if (SUCCEEDED(hr)) { + mal_result result = mal_context_get_device_info_from_MMDevice__wasapi(pContext, pMMDevice, mal_share_mode_shared, MAL_TRUE, &deviceInfo); // MAL_TRUE = onlySimpleInfo. + + mal_IMMDevice_Release(pMMDevice); + if (result == MAL_SUCCESS) { + mal_bool32 cbResult = callback(pContext, deviceType, &deviceInfo, pUserData); + if (cbResult == MAL_FALSE) { + break; + } + } + } + } + + return MAL_SUCCESS; +} + +mal_result mal_context_enumerate_devices__wasapi(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +{ + mal_assert(pContext != NULL); + mal_assert(callback != NULL); + + // Different enumeration for desktop and UWP. +#ifdef MAL_WIN32_DESKTOP + // Desktop + mal_IMMDeviceEnumerator* pDeviceEnumerator; + HRESULT hr = mal_CoCreateInstance(pContext, MAL_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MAL_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); + if (FAILED(hr)) { + return mal_context_post_error(pContext, NULL, "[WASAPI] Failed to create device enumerator.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + mal_IMMDeviceCollection* pDeviceCollection; + + // Playback. + hr = mal_IMMDeviceEnumerator_EnumAudioEndpoints(pDeviceEnumerator, mal_eRender, MAL_MM_DEVICE_STATE_ACTIVE, &pDeviceCollection); + if (SUCCEEDED(hr)) { + mal_context_enumerate_device_collection__wasapi(pContext, pDeviceCollection, mal_device_type_playback, callback, pUserData); + mal_IMMDeviceCollection_Release(pDeviceCollection); + } + + // Capture. + hr = mal_IMMDeviceEnumerator_EnumAudioEndpoints(pDeviceEnumerator, mal_eCapture, MAL_MM_DEVICE_STATE_ACTIVE, &pDeviceCollection); + if (SUCCEEDED(hr)) { + mal_context_enumerate_device_collection__wasapi(pContext, pDeviceCollection, mal_device_type_capture, callback, pUserData); + mal_IMMDeviceCollection_Release(pDeviceCollection); + } + + mal_IMMDeviceEnumerator_Release(pDeviceEnumerator); #else - #define IAudioCaptureClient_Release(p) ((IAudioCaptureClient*)p)->lpVtbl->Release((IAudioCaptureClient*)p) -#endif -#ifdef __cplusplus - #define IAudioCaptureClient_GetNextPacketSize(p, a) ((IAudioCaptureClient*)p)->GetNextPacketSize(a) -#else - #define IAudioCaptureClient_GetNextPacketSize(p, a) ((IAudioCaptureClient*)p)->lpVtbl->GetNextPacketSize((IAudioCaptureClient*)p, a) -#endif -#ifdef __cplusplus - #define IAudioCaptureClient_GetBuffer(p, a, b, c, d, e) ((IAudioCaptureClient*)p)->GetBuffer(a, b, c, d, e) -#else - #define IAudioCaptureClient_GetBuffer(p, a, b, c, d, e) ((IAudioCaptureClient*)p)->lpVtbl->GetBuffer((IAudioCaptureClient*)p, a, b, c, d, e) -#endif -#ifdef __cplusplus - #define IAudioCaptureClient_ReleaseBuffer(p, a) ((IAudioCaptureClient*)p)->ReleaseBuffer(a) -#else - #define IAudioCaptureClient_ReleaseBuffer(p, a) ((IAudioCaptureClient*)p)->lpVtbl->ReleaseBuffer((IAudioCaptureClient*)p, a) + // UWP + // + // The MMDevice API is only supported on desktop applications. For now, while I'm still figuring out how to properly enumerate + // over devices without using MMDevice, I'm restricting devices to defaults. + // + // Hint: DeviceInformation::FindAllAsync() with DeviceClass.AudioCapture/AudioRender. https://blogs.windows.com/buildingapps/2014/05/15/real-time-audio-in-windows-store-and-windows-phone-apps/ + if (callback) { + mal_bool32 cbResult = MAL_TRUE; + + // Playback. + if (cbResult) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); + } + + // Capture. + if (cbResult) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); + } + } #endif + return MAL_SUCCESS; +} + +mal_result mal_context_get_device_info__wasapi(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +{ + mal_IMMDeviceEnumerator* pDeviceEnumerator; + HRESULT hr = mal_CoCreateInstance(pContext, MAL_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MAL_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); + if (FAILED(hr)) { + return mal_context_post_error(pContext, NULL, "[WASAPI] Failed to create IMMDeviceEnumerator.", MAL_FAILED_TO_INIT_BACKEND); + } + + mal_IMMDevice* pMMDevice = NULL; + if (pDeviceID == NULL) { + hr = mal_IMMDeviceEnumerator_GetDefaultAudioEndpoint(pDeviceEnumerator, (deviceType == mal_device_type_playback) ? mal_eRender : mal_eCapture, mal_eConsole, &pMMDevice); + } else { + hr = mal_IMMDeviceEnumerator_GetDevice(pDeviceEnumerator, pDeviceID->wasapi, &pMMDevice); + } + + mal_IMMDeviceEnumerator_Release(pDeviceEnumerator); + if (FAILED(hr)) { + return mal_context_post_error(pContext, NULL, "[WASAPI] Failed to retrieve IMMDevice.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + mal_result result = mal_context_get_device_info_from_MMDevice__wasapi(pContext, pMMDevice, shareMode, MAL_FALSE, pDeviceInfo); // MAL_FALSE = !onlySimpleInfo. + + mal_IMMDevice_Release(pMMDevice); + return result; +} + + mal_result mal_context_init__wasapi(mal_context* pContext) { mal_assert(pContext != NULL); (void)pContext; + mal_result result = MAL_SUCCESS; + #ifdef MAL_WIN32_DESKTOP // WASAPI is only supported in Vista SP1 and newer. The reason for SP1 and not the base version of Vista is that event-driven // exclusive mode does not work until SP1. @@ -3461,13 +4899,21 @@ mal_result mal_context_init__wasapi(mal_context* pContext) osvi.dwMinorVersion = LOBYTE(_WIN32_WINNT_VISTA); osvi.wServicePackMajor = 1; if (VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, VerSetConditionMask(VerSetConditionMask(VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL), VER_MINORVERSION, VER_GREATER_EQUAL), VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL))) { - return MAL_SUCCESS; + result = MAL_SUCCESS; } else { - return MAL_NO_BACKEND; + result = MAL_NO_BACKEND; } -#else - return MAL_SUCCESS; #endif + + if (result != MAL_SUCCESS) { + return result; + } + + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__wasapi; + pContext->onEnumDevices = mal_context_enumerate_devices__wasapi; + pContext->onGetDeviceInfo = mal_context_get_device_info__wasapi; + + return result; } mal_result mal_context_uninit__wasapi(mal_context* pContext) @@ -3479,121 +4925,18 @@ mal_result mal_context_uninit__wasapi(mal_context* pContext) return MAL_SUCCESS; } -static mal_result mal_enumerate_devices__wasapi(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) -{ - mal_uint32 infoSize = *pCount; - *pCount = 0; - -#ifdef MAL_WIN32_DESKTOP - IMMDeviceEnumerator* pDeviceEnumerator; - HRESULT hr = mal_CoCreateInstance(pContext, g_malCLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, g_malIID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); - if (FAILED(hr)) { - return mal_context_post_error(pContext, NULL, "[WASAPI] Failed to create device enumerator.", MAL_WASAPI_FAILED_TO_CREATE_DEVICE_ENUMERATOR); - } - - IMMDeviceCollection* pDeviceCollection; - hr = IMMDeviceEnumerator_EnumAudioEndpoints(pDeviceEnumerator, (type == mal_device_type_playback) ? eRender : eCapture, DEVICE_STATE_ACTIVE, &pDeviceCollection); - if (FAILED(hr)) { - IMMDeviceEnumerator_Release(pDeviceEnumerator); - return mal_context_post_error(pContext, NULL, "[WASAPI] Failed to enumerate audio endpoints.", MAL_NO_DEVICE); - } - - IMMDeviceEnumerator_Release(pDeviceEnumerator); - - UINT count; - hr = IMMDeviceCollection_GetCount(pDeviceCollection, &count); - if (FAILED(hr)) { - IMMDeviceCollection_Release(pDeviceCollection); - return mal_context_post_error(pContext, NULL, "[WASAPI] Failed to get device count.", MAL_NO_DEVICE); - } - - for (mal_uint32 iDevice = 0; iDevice < count; ++iDevice) { - if (pInfo != NULL) { - if (infoSize > 0) { - mal_zero_object(pInfo); - - IMMDevice* pDevice; - hr = IMMDeviceCollection_Item(pDeviceCollection, iDevice, &pDevice); - if (SUCCEEDED(hr)) { - // ID. - LPWSTR id; - hr = IMMDevice_GetId(pDevice, &id); - if (SUCCEEDED(hr)) { - size_t idlen = wcslen(id); - if (idlen+sizeof(wchar_t) > sizeof(pInfo->id.wasapi)) { - mal_CoTaskMemFree(pContext, id); - mal_assert(MAL_FALSE); // NOTE: If this is triggered, please report it. It means the format of the ID must haved change and is too long to fit in our fixed sized buffer. - continue; - } - - memcpy(pInfo->id.wasapi, id, idlen * sizeof(wchar_t)); - pInfo->id.wasapi[idlen] = '\0'; - - mal_CoTaskMemFree(pContext, id); - } - - // Description / Friendly Name. - IPropertyStore *pProperties; - hr = IMMDevice_OpenPropertyStore(pDevice, STGM_READ, &pProperties); - if (SUCCEEDED(hr)) { - PROPVARIANT varName; - PropVariantInit(&varName); - hr = IPropertyStore_GetValue(pProperties, g_malPKEY_Device_FriendlyName, &varName); - if (SUCCEEDED(hr)) { - WideCharToMultiByte(CP_UTF8, 0, varName.pwszVal, -1, pInfo->name, sizeof(pInfo->name), 0, FALSE); - mal_PropVariantClear(pContext, &varName); - } - - IPropertyStore_Release(pProperties); - } - } - - pInfo += 1; - infoSize -= 1; - *pCount += 1; - } - } else { - *pCount += 1; - } - } - - IMMDeviceCollection_Release(pDeviceCollection); -#else - // The MMDevice API is only supported on desktop applications. For now, while I'm still figuring out how to properly enumerate - // over devices without using MMDevice, I'm restricting devices to defaults. - if (pInfo != NULL) { - if (infoSize > 0) { - if (type == mal_device_type_playback) { - mal_copy_memory(pInfo->id.wasapi, &g_malIID_DEVINTERFACE_AUDIO_RENDER, sizeof(g_malIID_DEVINTERFACE_AUDIO_RENDER)); - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "Default Playback Device", (size_t)-1); - } else { - mal_copy_memory(pInfo->id.wasapi, &g_malIID_DEVINTERFACE_AUDIO_CAPTURE, sizeof(g_malIID_DEVINTERFACE_AUDIO_CAPTURE)); - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "Default Capture Device", (size_t)-1); - } - - pInfo += 1; - *pCount += 1; - } - } else { - *pCount += 1; - } -#endif - - return MAL_SUCCESS; -} - -static void mal_device_uninit__wasapi(mal_device* pDevice) +void mal_device_uninit__wasapi(mal_device* pDevice) { mal_assert(pDevice != NULL); if (pDevice->wasapi.pRenderClient) { - IAudioRenderClient_Release(pDevice->wasapi.pRenderClient); + mal_IAudioRenderClient_Release((mal_IAudioRenderClient*)pDevice->wasapi.pRenderClient); } if (pDevice->wasapi.pCaptureClient) { - IAudioCaptureClient_Release(pDevice->wasapi.pCaptureClient); + mal_IAudioCaptureClient_Release((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); } if (pDevice->wasapi.pAudioClient) { - IAudioClient_Release(pDevice->wasapi.pAudioClient); + mal_IAudioClient_Release((mal_IAudioClient*)pDevice->wasapi.pAudioClient); } if (pDevice->wasapi.hEvent) { @@ -3604,9 +4947,13 @@ static void mal_device_uninit__wasapi(mal_device* pDevice) } } +// This is the part that's preventing mini_al from being compiled as C with UWP. We need to implement IActivateAudioInterfaceCompletionHandler +// in C which is quite annoying. #ifndef MAL_WIN32_DESKTOP #ifdef __cplusplus + #include #include + class malCompletionHandler : public Microsoft::WRL::RuntimeClass< Microsoft::WRL::RuntimeClassFlags< Microsoft::WRL::ClassicCom >, Microsoft::WRL::FtmBase, IActivateAudioInterfaceCompletionHandler > { public: @@ -3653,7 +5000,7 @@ static void mal_device_uninit__wasapi(mal_device* pDevice) #endif #endif // !MAL_WIN32_DESKTOP -static mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) { (void)pContext; @@ -3663,60 +5010,41 @@ static mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type HRESULT hr; mal_result result = MAL_SUCCESS; const char* errorMsg = ""; - AUDCLNT_SHAREMODE shareMode = AUDCLNT_SHAREMODE_SHARED; - - WAVEFORMATEXTENSIBLE wf; - mal_zero_object(&wf); - wf.Format.cbSize = sizeof(wf); - wf.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - wf.Format.nChannels = (WORD)pDevice->channels; - wf.Format.nSamplesPerSec = (DWORD)pDevice->sampleRate; - wf.Format.wBitsPerSample = (WORD)mal_get_sample_size_in_bytes(pDevice->format)*8; - wf.Format.nBlockAlign = (wf.Format.nChannels * wf.Format.wBitsPerSample) / 8; - wf.Format.nAvgBytesPerSec = wf.Format.nBlockAlign * wf.Format.nSamplesPerSec; - wf.Samples.wValidBitsPerSample = wf.Format.wBitsPerSample; - wf.dwChannelMask = mal_channel_map_to_channel_mask__win32(pDevice->channelMap, pDevice->channels); - if (pDevice->format == mal_format_f32) { - wf.SubFormat = MAL_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - } else { - wf.SubFormat = MAL_GUID_KSDATAFORMAT_SUBTYPE_PCM; - } + MAL_AUDCLNT_SHAREMODE shareMode = MAL_AUDCLNT_SHAREMODE_SHARED; + MAL_REFERENCE_TIME bufferDurationInMicroseconds = ((mal_uint64)pDevice->bufferSizeInFrames * 1000 * 1000) / pConfig->sampleRate; + WAVEFORMATEXTENSIBLE* pBestFormatTemp = NULL; #ifdef MAL_WIN32_DESKTOP - IMMDevice* pMMDevice = NULL; + mal_IMMDevice* pMMDevice = NULL; - IMMDeviceEnumerator* pDeviceEnumerator; - hr = mal_CoCreateInstance(pContext, g_malCLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, g_malIID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); + mal_IMMDeviceEnumerator* pDeviceEnumerator; + hr = mal_CoCreateInstance(pContext, MAL_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MAL_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); if (FAILED(hr)) { - errorMsg = "[WASAPI] Failed to create IMMDeviceEnumerator.", result = MAL_WASAPI_FAILED_TO_CREATE_DEVICE_ENUMERATOR; + errorMsg = "[WASAPI] Failed to create IMMDeviceEnumerator.", result = MAL_FAILED_TO_OPEN_BACKEND_DEVICE; goto done; } if (pDeviceID == NULL) { - hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pDeviceEnumerator, (type == mal_device_type_playback) ? eRender : eCapture, eConsole, &pMMDevice); - if (FAILED(hr)) { - IMMDeviceEnumerator_Release(pDeviceEnumerator); - errorMsg = "[WASAPI] Failed to create default backend device.", result = MAL_WASAPI_FAILED_TO_CREATE_DEVICE; - goto done; - } + hr = mal_IMMDeviceEnumerator_GetDefaultAudioEndpoint(pDeviceEnumerator, (type == mal_device_type_playback) ? mal_eRender : mal_eCapture, mal_eConsole, &pMMDevice); } else { - hr = IMMDeviceEnumerator_GetDevice(pDeviceEnumerator, pDeviceID->wasapi, &pMMDevice); - if (FAILED(hr)) { - IMMDeviceEnumerator_Release(pDeviceEnumerator); - errorMsg = "[WASAPI] Failed to create backend device.", result = MAL_WASAPI_FAILED_TO_CREATE_DEVICE; - goto done; - } + hr = mal_IMMDeviceEnumerator_GetDevice(pDeviceEnumerator, pDeviceID->wasapi, &pMMDevice); } - IMMDeviceEnumerator_Release(pDeviceEnumerator); - - hr = IMMDevice_Activate(pMMDevice, g_malIID_IAudioClient, CLSCTX_ALL, NULL, &pDevice->wasapi.pAudioClient); if (FAILED(hr)) { - errorMsg = "[WASAPI] Failed to activate device.", result = MAL_WASAPI_FAILED_TO_ACTIVATE_DEVICE; + mal_IMMDeviceEnumerator_Release(pDeviceEnumerator); + errorMsg = "[WASAPI] Failed to create backend device.", result = MAL_FAILED_TO_OPEN_BACKEND_DEVICE; + goto done; + } + + mal_IMMDeviceEnumerator_Release(pDeviceEnumerator); + + hr = mal_IMMDevice_Activate(pMMDevice, &MAL_IID_IAudioClient, CLSCTX_ALL, NULL, &pDevice->wasapi.pAudioClient); + if (FAILED(hr)) { + errorMsg = "[WASAPI] Failed to activate device.", result = MAL_FAILED_TO_OPEN_BACKEND_DEVICE; goto done; } #else - IActivateAudioInterfaceAsyncOperation *pAsyncOp = NULL; + mal_IActivateAudioInterfaceAsyncOperation *pAsyncOp = NULL; malCompletionHandler completionHandler; IID iid; @@ -3724,9 +5052,9 @@ static mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type mal_copy_memory(&iid, pDeviceID->wasapi, sizeof(iid)); } else { if (type == mal_device_type_playback) { - iid = g_malIID_DEVINTERFACE_AUDIO_RENDER; + iid = MAL_IID_DEVINTERFACE_AUDIO_RENDER; } else { - iid = g_malIID_DEVINTERFACE_AUDIO_CAPTURE; + iid = MAL_IID_DEVINTERFACE_AUDIO_CAPTURE; } } @@ -3741,16 +5069,16 @@ static mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type if (result != MAL_SUCCESS) { mal_CoTaskMemFree(pContext, iidStr); - errorMsg = "[WASAPI] Failed to create event for waiting for ActivateAudioInterfaceAsync().", result = MAL_WASAPI_FAILED_TO_ACTIVATE_DEVICE; + errorMsg = "[WASAPI] Failed to create event for waiting for ActivateAudioInterfaceAsync().", result = MAL_FAILED_TO_OPEN_BACKEND_DEVICE; goto done; } - hr = ActivateAudioInterfaceAsync(iidStr, g_malIID_IAudioClient, NULL, &completionHandler, &pAsyncOp); + hr = ActivateAudioInterfaceAsync(iidStr, MAL_IID_IAudioClient, NULL, (IActivateAudioInterfaceCompletionHandler*)&completionHandler, (IActivateAudioInterfaceAsyncOperation**)&pAsyncOp); if (FAILED(hr)) { completionHandler.Uninit(); mal_CoTaskMemFree(pContext, iidStr); - errorMsg = "[WASAPI] ActivateAudioInterfaceAsync() failed.", result = MAL_WASAPI_FAILED_TO_ACTIVATE_DEVICE; + errorMsg = "[WASAPI] ActivateAudioInterfaceAsync() failed.", result = MAL_FAILED_TO_OPEN_BACKEND_DEVICE; goto done; } @@ -3762,62 +5090,103 @@ static mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type HRESULT activateResult; IUnknown* pActivatedInterface; - hr = IActivateAudioInterfaceAsyncOperation_GetActivateResult(pAsyncOp, &activateResult, &pActivatedInterface); + hr = mal_IActivateAudioInterfaceAsyncOperation_GetActivateResult(pAsyncOp, &activateResult, &pActivatedInterface); if (FAILED(hr) || FAILED(activateResult)) { - errorMsg = "[WASAPI] Failed to activate device.", result = MAL_WASAPI_FAILED_TO_ACTIVATE_DEVICE; + errorMsg = "[WASAPI] Failed to activate device.", result = MAL_FAILED_TO_OPEN_BACKEND_DEVICE; goto done; } // Here is where we grab the IAudioClient interface. - hr = pActivatedInterface->QueryInterface(g_malIID_IAudioClient, &pDevice->wasapi.pAudioClient); + hr = pActivatedInterface->QueryInterface(MAL_IID_IAudioClient, &pDevice->wasapi.pAudioClient); if (FAILED(hr)) { - errorMsg = "[WASAPI] Failed to query IAudioClient interface.", result = MAL_WASAPI_FAILED_TO_ACTIVATE_DEVICE; + errorMsg = "[WASAPI] Failed to query IAudioClient interface.", result = MAL_FAILED_TO_OPEN_BACKEND_DEVICE; goto done; } #endif - // Here is where we try to determine the best format to use with the device. If the client if wanting exclusive mode, first try finding the best format for that. If this fails, fall back to shared mode. - WAVEFORMATEXTENSIBLE* pBestFormatTemp = NULL; - result = MAL_FORMAT_NOT_SUPPORTED; - if (pConfig->preferExclusiveMode) { - hr = IAudioClient_IsFormatSupported(pDevice->wasapi.pAudioClient, AUDCLNT_SHAREMODE_EXCLUSIVE, (WAVEFORMATEX*)&wf, NULL); - #ifdef MAL_WIN32_DESKTOP - if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) { - // The format isn't supported, so retrieve the actual format from the property store and try that. - IPropertyStore* pStore = NULL; - hr = IMMDevice_OpenPropertyStore(pMMDevice, STGM_READ, &pStore); - if (SUCCEEDED(hr)) { - PROPVARIANT prop; - PropVariantInit(&prop); - hr = IPropertyStore_GetValue(pStore, g_malPKEY_AudioEngine_DeviceFormat, &prop); - if (SUCCEEDED(hr)) { - WAVEFORMATEX* pActualFormat = (WAVEFORMATEX*)prop.blob.pBlobData; - hr = IAudioClient_IsFormatSupported(pDevice->wasapi.pAudioClient, AUDCLNT_SHAREMODE_EXCLUSIVE, pActualFormat, NULL); - if (SUCCEEDED(hr)) { - mal_copy_memory(&wf, pActualFormat, sizeof(WAVEFORMATEXTENSIBLE)); - } + WAVEFORMATEXTENSIBLE wf; + mal_zero_object(&wf); + wf.Format.cbSize = sizeof(wf); + wf.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wf.Format.nChannels = (WORD)pDevice->channels; + wf.Format.nSamplesPerSec = (DWORD)pDevice->sampleRate; + wf.Format.wBitsPerSample = (WORD)mal_get_bytes_per_sample(pDevice->format)*8; + wf.Format.nBlockAlign = (wf.Format.nChannels * wf.Format.wBitsPerSample) / 8; + wf.Format.nAvgBytesPerSec = wf.Format.nBlockAlign * wf.Format.nSamplesPerSec; + wf.Samples.wValidBitsPerSample = /*(pDevice->format == mal_format_s24_32) ? 24 :*/ wf.Format.wBitsPerSample; + wf.dwChannelMask = mal_channel_map_to_channel_mask__win32(pDevice->channelMap, pDevice->channels); + if (pDevice->format == mal_format_f32) { + wf.SubFormat = MAL_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + } else { + wf.SubFormat = MAL_GUID_KSDATAFORMAT_SUBTYPE_PCM; + } - mal_PropVariantClear(pDevice->pContext, &prop); + + // Here is where we try to determine the best format to use with the device. If the client if wanting exclusive mode, first try finding the best format for that. If this fails, fall back to shared mode. + result = MAL_FORMAT_NOT_SUPPORTED; + if (pConfig->shareMode == mal_share_mode_exclusive) { + #ifdef MAL_WIN32_DESKTOP + // In exclusive mode on desktop we always use the backend's native format. + mal_IPropertyStore* pStore = NULL; + hr = mal_IMMDevice_OpenPropertyStore(pMMDevice, STGM_READ, &pStore); + if (SUCCEEDED(hr)) { + PROPVARIANT prop; + mal_PropVariantInit(&prop); + hr = mal_IPropertyStore_GetValue(pStore, &MAL_PKEY_AudioEngine_DeviceFormat, &prop); + if (SUCCEEDED(hr)) { + WAVEFORMATEX* pActualFormat = (WAVEFORMATEX*)prop.blob.pBlobData; + hr = mal_IAudioClient_IsFormatSupported((mal_IAudioClient*)pDevice->wasapi.pAudioClient, MAL_AUDCLNT_SHAREMODE_EXCLUSIVE, pActualFormat, NULL); + if (SUCCEEDED(hr)) { + mal_copy_memory(&wf, pActualFormat, sizeof(WAVEFORMATEXTENSIBLE)); } - IPropertyStore_Release(pStore); + mal_PropVariantClear(pDevice->pContext, &prop); } + + mal_IPropertyStore_Release(pStore); } + #else + // With non-Desktop builds we just try using the requested format. + hr = mal_IAudioClient_IsFormatSupported((mal_IAudioClient*)pDevice->wasapi.pAudioClient, MAL_AUDCLNT_SHAREMODE_EXCLUSIVE, (WAVEFORMATEX*)&wf, NULL); #endif if (hr == S_OK) { - shareMode = AUDCLNT_SHAREMODE_EXCLUSIVE; + shareMode = MAL_AUDCLNT_SHAREMODE_EXCLUSIVE; result = MAL_SUCCESS; } } // Fall back to shared mode if necessary. if (result != MAL_SUCCESS) { - hr = IAudioClient_IsFormatSupported(pDevice->wasapi.pAudioClient, AUDCLNT_SHAREMODE_SHARED, (WAVEFORMATEX*)&wf, (WAVEFORMATEX**)&pBestFormatTemp); + WAVEFORMATEXTENSIBLE* pNativeFormat = NULL; + hr = mal_IAudioClient_GetMixFormat((mal_IAudioClient*)pDevice->wasapi.pAudioClient, (WAVEFORMATEX**)&pNativeFormat); + if (hr == S_OK) { + if (pDevice->usingDefaultFormat) { + wf.Format.wBitsPerSample = pNativeFormat->Format.wBitsPerSample; + wf.Format.nBlockAlign = pNativeFormat->Format.nBlockAlign; + wf.Format.nAvgBytesPerSec = pNativeFormat->Format.nAvgBytesPerSec; + wf.Samples.wValidBitsPerSample = pNativeFormat->Samples.wValidBitsPerSample; + wf.SubFormat = pNativeFormat->SubFormat; + } + if (pDevice->usingDefaultChannels) { + wf.Format.nChannels = pNativeFormat->Format.nChannels; + } + if (pDevice->usingDefaultSampleRate) { + wf.Format.nSamplesPerSec = pNativeFormat->Format.nSamplesPerSec; + } + if (pDevice->usingDefaultChannelMap) { + wf.dwChannelMask = pNativeFormat->dwChannelMask; + } + + mal_CoTaskMemFree(pDevice->pContext, pNativeFormat); + pNativeFormat = NULL; + } + + hr = mal_IAudioClient_IsFormatSupported((mal_IAudioClient*)pDevice->wasapi.pAudioClient, MAL_AUDCLNT_SHAREMODE_SHARED, (WAVEFORMATEX*)&wf, (WAVEFORMATEX**)&pBestFormatTemp); if (hr != S_OK && hr != S_FALSE) { - hr = IAudioClient_GetMixFormat(pDevice->wasapi.pAudioClient, (WAVEFORMATEX**)&pBestFormatTemp); + hr = mal_IAudioClient_GetMixFormat((mal_IAudioClient*)pDevice->wasapi.pAudioClient, (WAVEFORMATEX**)&pBestFormatTemp); if (hr != S_OK) { - result = MAL_WASAPI_FAILED_TO_FIND_BEST_FORMAT; + result = MAL_FORMAT_NOT_SUPPORTED; } else { result = MAL_SUCCESS; } @@ -3825,12 +5194,12 @@ static mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type result = MAL_SUCCESS; } - shareMode = AUDCLNT_SHAREMODE_SHARED; + shareMode = MAL_AUDCLNT_SHAREMODE_SHARED; } // Return an error if we still haven't found a format. if (result != MAL_SUCCESS) { - errorMsg = "[WASAPI] Failed to find best device mix format.", result = MAL_WASAPI_FAILED_TO_ACTIVATE_DEVICE; + errorMsg = "[WASAPI] Failed to find best device mix format.", result = MAL_FORMAT_NOT_SUPPORTED; goto done; } @@ -3840,25 +5209,7 @@ static mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type } - REFERENCE_TIME bufferDurationInMicroseconds = ((mal_uint64)pDevice->bufferSizeInFrames * 1000 * 1000) / pConfig->sampleRate; - - if (mal_is_guid_equal(wf.SubFormat, MAL_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { - pDevice->internalFormat = mal_format_f32; - } else { - if (wf.Format.wBitsPerSample == 32) { - pDevice->internalFormat = mal_format_s32; - } else if (wf.Format.wBitsPerSample == 24) { - pDevice->internalFormat = mal_format_s24; - } else if (wf.Format.wBitsPerSample == 16) { - pDevice->internalFormat = mal_format_s16; - } else if (wf.Format.wBitsPerSample == 8) { - pDevice->internalFormat = mal_format_u8; - } else { - errorMsg = "[WASAPI] Device's native format is not supported.", result = MAL_FORMAT_NOT_SUPPORTED; - goto done; - } - } - + pDevice->internalFormat = mal_format_from_WAVEFORMATEX((WAVEFORMATEX*)&wf); pDevice->internalChannels = wf.Format.nChannels; pDevice->internalSampleRate = wf.Format.nSamplesPerSec; @@ -3866,75 +5217,93 @@ static mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type mal_channel_mask_to_channel_map__win32(wf.dwChannelMask, pDevice->internalChannels, pDevice->internalChannelMap); // Slightly different initialization for shared and exclusive modes. - if (shareMode == AUDCLNT_SHAREMODE_SHARED) { + if (shareMode == MAL_AUDCLNT_SHAREMODE_SHARED) { // Shared. - REFERENCE_TIME bufferDuration = bufferDurationInMicroseconds*10; - hr = IAudioClient_Initialize(pDevice->wasapi.pAudioClient, shareMode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, 0, (WAVEFORMATEX*)&wf, NULL); + MAL_REFERENCE_TIME bufferDuration = bufferDurationInMicroseconds*10; + hr = mal_IAudioClient_Initialize((mal_IAudioClient*)pDevice->wasapi.pAudioClient, shareMode, MAL_AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, 0, (WAVEFORMATEX*)&wf, NULL); if (FAILED(hr)) { if (hr == E_ACCESSDENIED) { errorMsg = "[WASAPI] Failed to initialize device. Access denied.", result = MAL_ACCESS_DENIED; } else { - errorMsg = "[WASAPI] Failed to initialize device.", result = MAL_WASAPI_FAILED_TO_INITIALIZE_DEVICE; + errorMsg = "[WASAPI] Failed to initialize device.", result = MAL_FAILED_TO_OPEN_BACKEND_DEVICE; } goto done; } } else { // Exclusive. - REFERENCE_TIME bufferDuration = bufferDurationInMicroseconds*10; - hr = IAudioClient_Initialize(pDevice->wasapi.pAudioClient, shareMode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, bufferDuration, (WAVEFORMATEX*)&wf, NULL); - if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { + MAL_REFERENCE_TIME bufferDuration = bufferDurationInMicroseconds*10; + hr = mal_IAudioClient_Initialize((mal_IAudioClient*)pDevice->wasapi.pAudioClient, shareMode, MAL_AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, bufferDuration, (WAVEFORMATEX*)&wf, NULL); + if (hr == MAL_AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { UINT bufferSizeInFrames; - hr = IAudioClient_GetBufferSize(pDevice->wasapi.pAudioClient, &bufferSizeInFrames); + hr = mal_IAudioClient_GetBufferSize((mal_IAudioClient*)pDevice->wasapi.pAudioClient, &bufferSizeInFrames); if (SUCCEEDED(hr)) { - bufferDuration = (REFERENCE_TIME)((10000.0 * 1000 / wf.Format.nSamplesPerSec * bufferSizeInFrames) + 0.5); + bufferDuration = (MAL_REFERENCE_TIME)((10000.0 * 1000 / wf.Format.nSamplesPerSec * bufferSizeInFrames) + 0.5); // Unfortunately we need to release and re-acquire the audio client according to MSDN. Seems silly - why not just call IAudioClient_Initialize() again?! - IAudioClient_Release(pDevice->wasapi.pAudioClient); + mal_IAudioClient_Release((mal_IAudioClient*)pDevice->wasapi.pAudioClient); #ifdef MAL_WIN32_DESKTOP - hr = IMMDevice_Activate(pMMDevice, g_malIID_IAudioClient, CLSCTX_ALL, NULL, &pDevice->wasapi.pAudioClient); + hr = mal_IMMDevice_Activate(pMMDevice, &MAL_IID_IAudioClient, CLSCTX_ALL, NULL, &pDevice->wasapi.pAudioClient); #else - hr = pActivatedInterface->QueryInterface(g_malIID_IAudioClient, &pDevice->wasapi.pAudioClient); + hr = pActivatedInterface->QueryInterface(MAL_IID_IAudioClient, &pDevice->wasapi.pAudioClient); #endif if (SUCCEEDED(hr)) { - hr = IAudioClient_Initialize(pDevice->wasapi.pAudioClient, shareMode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, bufferDuration, (WAVEFORMATEX*)&wf, NULL); + hr = mal_IAudioClient_Initialize((mal_IAudioClient*)pDevice->wasapi.pAudioClient, shareMode, MAL_AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, bufferDuration, (WAVEFORMATEX*)&wf, NULL); } } } if (FAILED(hr)) { - errorMsg = "[WASAPI] Failed to initialize device.", result = MAL_WASAPI_FAILED_TO_INITIALIZE_DEVICE; + errorMsg = "[WASAPI] Failed to initialize device.", result = MAL_FAILED_TO_OPEN_BACKEND_DEVICE; goto done; } } - hr = IAudioClient_GetBufferSize(pDevice->wasapi.pAudioClient, &pDevice->bufferSizeInFrames); + hr = mal_IAudioClient_GetBufferSize((mal_IAudioClient*)pDevice->wasapi.pAudioClient, &pDevice->bufferSizeInFrames); if (FAILED(hr)) { - errorMsg = "[WASAPI] Failed to get audio client's actual buffer size.", result = MAL_WASAPI_FAILED_TO_INITIALIZE_DEVICE; + errorMsg = "[WASAPI] Failed to get audio client's actual buffer size.", result = MAL_FAILED_TO_OPEN_BACKEND_DEVICE; goto done; } if (type == mal_device_type_playback) { - hr = IAudioClient_GetService((IAudioClient*)pDevice->wasapi.pAudioClient, g_malIID_IAudioRenderClient, &pDevice->wasapi.pRenderClient); + hr = mal_IAudioClient_GetService((mal_IAudioClient*)pDevice->wasapi.pAudioClient, &MAL_IID_IAudioRenderClient, &pDevice->wasapi.pRenderClient); } else { - hr = IAudioClient_GetService((IAudioClient*)pDevice->wasapi.pAudioClient, g_malIID_IAudioCaptureClient, &pDevice->wasapi.pCaptureClient); + hr = mal_IAudioClient_GetService((mal_IAudioClient*)pDevice->wasapi.pAudioClient, &MAL_IID_IAudioCaptureClient, &pDevice->wasapi.pCaptureClient); } if (FAILED(hr)) { - errorMsg = "[WASAPI] Failed to get audio client service.", result = MAL_WASAPI_FAILED_TO_INITIALIZE_DEVICE; + errorMsg = "[WASAPI] Failed to get audio client service.", result = MAL_API_NOT_FOUND; goto done; } - if (shareMode == AUDCLNT_SHAREMODE_SHARED) { + if (shareMode == MAL_AUDCLNT_SHAREMODE_SHARED) { pDevice->exclusiveMode = MAL_FALSE; - } else /*if (shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE)*/ { + } else /*if (shareMode == MAL_AUDCLNT_SHAREMODE_EXCLUSIVE)*/ { pDevice->exclusiveMode = MAL_TRUE; } + // Grab the name of the device. +#ifdef MAL_WIN32_DESKTOP + mal_IPropertyStore *pProperties; + hr = mal_IMMDevice_OpenPropertyStore(pMMDevice, STGM_READ, &pProperties); + if (SUCCEEDED(hr)) { + PROPVARIANT varName; + mal_PropVariantInit(&varName); + hr = mal_IPropertyStore_GetValue(pProperties, &MAL_PKEY_Device_FriendlyName, &varName); + if (SUCCEEDED(hr)) { + WideCharToMultiByte(CP_UTF8, 0, varName.pwszVal, -1, pDevice->name, sizeof(pDevice->name), 0, FALSE); + mal_PropVariantClear(pContext, &varName); + } + + mal_IPropertyStore_Release(pProperties); + } +#endif + + // We need to create and set the event for event-driven mode. This event is signalled whenever a new chunk of audio // data needs to be written or read from the device. pDevice->wasapi.hEvent = CreateEventA(NULL, FALSE, FALSE, NULL); @@ -3943,7 +5312,7 @@ static mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type goto done; } - IAudioClient_SetEventHandle(pDevice->wasapi.pAudioClient, pDevice->wasapi.hEvent); + mal_IAudioClient_SetEventHandle((mal_IAudioClient*)pDevice->wasapi.pAudioClient, pDevice->wasapi.hEvent); // When the device is playing the worker thread will be waiting on a bunch of notification events. To return from @@ -3960,11 +5329,11 @@ done: // Clean up. #ifdef MAL_WIN32_DESKTOP if (pMMDevice != NULL) { - IMMDevice_Release(pMMDevice); + mal_IMMDevice_Release(pMMDevice); } #else if (pAsyncOp != NULL) { - IActivateAudioInterfaceAsyncOperation_Release(pAsyncOp); + mal_IActivateAudioInterfaceAsyncOperation_Release(pAsyncOp); } #endif @@ -3976,27 +5345,27 @@ done: } } -static mal_result mal_device__start_backend__wasapi(mal_device* pDevice) +mal_result mal_device__start_backend__wasapi(mal_device* pDevice) { mal_assert(pDevice != NULL); // Playback devices need to have an initial chunk of data loaded. if (pDevice->type == mal_device_type_playback) { BYTE* pData; - HRESULT hr = IAudioRenderClient_GetBuffer(pDevice->wasapi.pRenderClient, pDevice->bufferSizeInFrames, &pData); + HRESULT hr = mal_IAudioRenderClient_GetBuffer((mal_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->bufferSizeInFrames, &pData); if (FAILED(hr)) { - return mal_post_error(pDevice, "[WASAPI] Failed to retrieve buffer from internal playback device.", MAL_WASAPI_FAILED_TO_GET_INTERNAL_BUFFER); + return mal_post_error(pDevice, "[WASAPI] Failed to retrieve buffer from internal playback device.", MAL_FAILED_TO_MAP_DEVICE_BUFFER); } mal_device__read_frames_from_client(pDevice, pDevice->bufferSizeInFrames, pData); - hr = IAudioRenderClient_ReleaseBuffer(pDevice->wasapi.pRenderClient, pDevice->bufferSizeInFrames, 0); + hr = mal_IAudioRenderClient_ReleaseBuffer((mal_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->bufferSizeInFrames, 0); if (FAILED(hr)) { - return mal_post_error(pDevice, "[WASAPI] Failed to release internal buffer for playback device.", MAL_WASAPI_FAILED_TO_RELEASE_INTERNAL_BUFFER); + return mal_post_error(pDevice, "[WASAPI] Failed to release internal buffer for playback device.", MAL_FAILED_TO_UNMAP_DEVICE_BUFFER); } } - HRESULT hr = IAudioClient_Start(pDevice->wasapi.pAudioClient); + HRESULT hr = mal_IAudioClient_Start((mal_IAudioClient*)pDevice->wasapi.pAudioClient); if (FAILED(hr)) { return mal_post_error(pDevice, "[WASAPI] Failed to start internal device.", MAL_FAILED_TO_START_BACKEND_DEVICE); } @@ -4004,11 +5373,11 @@ static mal_result mal_device__start_backend__wasapi(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__stop_backend__wasapi(mal_device* pDevice) +mal_result mal_device__stop_backend__wasapi(mal_device* pDevice) { mal_assert(pDevice != NULL); - HRESULT hr = IAudioClient_Stop(pDevice->wasapi.pAudioClient); + HRESULT hr = mal_IAudioClient_Stop((mal_IAudioClient*)pDevice->wasapi.pAudioClient); if (FAILED(hr)) { return mal_post_error(pDevice, "[WASAPI] Failed to stop internal device.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); } @@ -4016,7 +5385,7 @@ static mal_result mal_device__stop_backend__wasapi(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__break_main_loop__wasapi(mal_device* pDevice) +mal_result mal_device__break_main_loop__wasapi(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -4027,14 +5396,14 @@ static mal_result mal_device__break_main_loop__wasapi(mal_device* pDevice) return MAL_SUCCESS; } -static mal_uint32 mal_device__get_available_frames__wasapi(mal_device* pDevice) +mal_uint32 mal_device__get_available_frames__wasapi(mal_device* pDevice) { mal_assert(pDevice != NULL); #if 1 if (pDevice->type == mal_device_type_playback) { UINT32 paddingFramesCount; - HRESULT hr = IAudioClient_GetCurrentPadding(pDevice->wasapi.pAudioClient, &paddingFramesCount); + HRESULT hr = mal_IAudioClient_GetCurrentPadding((mal_IAudioClient*)pDevice->wasapi.pAudioClient, &paddingFramesCount); if (FAILED(hr)) { return 0; } @@ -4046,7 +5415,7 @@ static mal_uint32 mal_device__get_available_frames__wasapi(mal_device* pDevice) } } else { UINT32 framesAvailable; - HRESULT hr = IAudioCaptureClient_GetNextPacketSize(pDevice->wasapi.pCaptureClient, &framesAvailable); + HRESULT hr = mal_IAudioCaptureClient_GetNextPacketSize((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, &framesAvailable); if (FAILED(hr)) { return 0; } @@ -4055,7 +5424,7 @@ static mal_uint32 mal_device__get_available_frames__wasapi(mal_device* pDevice) } #else UINT32 paddingFramesCount; - HRESULT hr = IAudioClient_GetCurrentPadding(pDevice->wasapi.pAudioClient, &paddingFramesCount); + HRESULT hr = mal_IAudioClient_GetCurrentPadding(pDevice->wasapi.pAudioClient, &paddingFramesCount); if (FAILED(hr)) { return 0; } @@ -4068,7 +5437,7 @@ static mal_uint32 mal_device__get_available_frames__wasapi(mal_device* pDevice) #endif } -static mal_uint32 mal_device__wait_for_frames__wasapi(mal_device* pDevice) +mal_uint32 mal_device__wait_for_frames__wasapi(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -4097,7 +5466,7 @@ static mal_uint32 mal_device__wait_for_frames__wasapi(mal_device* pDevice) return mal_device__get_available_frames__wasapi(pDevice); } -static mal_result mal_device__main_loop__wasapi(mal_device* pDevice) +mal_result mal_device__main_loop__wasapi(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -4118,16 +5487,16 @@ static mal_result mal_device__main_loop__wasapi(mal_device* pDevice) if (pDevice->type == mal_device_type_playback) { BYTE* pData; - HRESULT hr = IAudioRenderClient_GetBuffer(pDevice->wasapi.pRenderClient, framesAvailable, &pData); + HRESULT hr = mal_IAudioRenderClient_GetBuffer((mal_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailable, &pData); if (FAILED(hr)) { - return mal_post_error(pDevice, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for sending new data to the device.", MAL_WASAPI_FAILED_TO_GET_INTERNAL_BUFFER); + return mal_post_error(pDevice, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for sending new data to the device.", MAL_FAILED_TO_MAP_DEVICE_BUFFER); } mal_device__read_frames_from_client(pDevice, framesAvailable, pData); - hr = IAudioRenderClient_ReleaseBuffer(pDevice->wasapi.pRenderClient, framesAvailable, 0); + hr = mal_IAudioRenderClient_ReleaseBuffer((mal_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailable, 0); if (FAILED(hr)) { - return mal_post_error(pDevice, "[WASAPI] Failed to release internal buffer from playback device in preparation for sending new data to the device.", MAL_WASAPI_FAILED_TO_RELEASE_INTERNAL_BUFFER); + return mal_post_error(pDevice, "[WASAPI] Failed to release internal buffer from playback device in preparation for sending new data to the device.", MAL_FAILED_TO_UNMAP_DEVICE_BUFFER); } } else { UINT32 framesRemaining = framesAvailable; @@ -4135,18 +5504,18 @@ static mal_result mal_device__main_loop__wasapi(mal_device* pDevice) BYTE* pData; UINT32 framesToSend; DWORD flags; - HRESULT hr = IAudioCaptureClient_GetBuffer(pDevice->wasapi.pCaptureClient, &pData, &framesToSend, &flags, NULL, NULL); + HRESULT hr = mal_IAudioCaptureClient_GetBuffer((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, &pData, &framesToSend, &flags, NULL, NULL); if (FAILED(hr)) { - mal_post_error(pDevice, "[WASAPI] WARNING: Failed to retrieve internal buffer from capture device in preparation for sending new data to the client.", MAL_WASAPI_FAILED_TO_GET_INTERNAL_BUFFER); + mal_post_error(pDevice, "[WASAPI] WARNING: Failed to retrieve internal buffer from capture device in preparation for sending new data to the client.", MAL_FAILED_TO_MAP_DEVICE_BUFFER); break; } - if (hr != AUDCLNT_S_BUFFER_EMPTY) { + if (hr != MAL_AUDCLNT_S_BUFFER_EMPTY) { mal_device__send_frames_to_client(pDevice, framesToSend, pData); - hr = IAudioCaptureClient_ReleaseBuffer(pDevice->wasapi.pCaptureClient, framesToSend); + hr = mal_IAudioCaptureClient_ReleaseBuffer((mal_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, framesToSend); if (FAILED(hr)) { - mal_post_error(pDevice, "[WASAPI] WARNING: Failed to release internal buffer from capture device in preparation for sending new data to the client.", MAL_WASAPI_FAILED_TO_RELEASE_INTERNAL_BUFFER); + mal_post_error(pDevice, "[WASAPI] WARNING: Failed to release internal buffer from capture device in preparation for sending new data to the client.", MAL_FAILED_TO_UNMAP_DEVICE_BUFFER); break; } @@ -4170,35 +5539,534 @@ static mal_result mal_device__main_loop__wasapi(mal_device* pDevice) // /////////////////////////////////////////////////////////////////////////////// #ifdef MAL_HAS_DSOUND -#include +//#include -#if 0 // MAL_GUID_NULL is not currently used, but leaving it here in case I need to add it back again. -static GUID MAL_GUID_NULL = {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -#endif -static GUID MAL_GUID_IID_DirectSoundNotify = {0xb0210783, 0x89cd, 0x11d0, {0xaf, 0x08, 0x00, 0xa0, 0xc9, 0x25, 0xcd, 0x16}}; -static GUID MAL_GUID_IID_IDirectSoundCaptureBuffer = {0xb0210782, 0x89cd, 0x11d0, {0xaf, 0x08, 0x00, 0xa0, 0xc9, 0x25, 0xcd, 0x16}}; +GUID MAL_GUID_IID_DirectSoundNotify = {0xb0210783, 0x89cd, 0x11d0, {0xaf, 0x08, 0x00, 0xa0, 0xc9, 0x25, 0xcd, 0x16}}; -typedef HRESULT (WINAPI * mal_DirectSoundCreateProc)(const GUID* pcGuidDevice, LPDIRECTSOUND *ppDS8, LPUNKNOWN pUnkOuter); -typedef HRESULT (WINAPI * mal_DirectSoundEnumerateAProc)(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext); -typedef HRESULT (WINAPI * mal_DirectSoundCaptureCreateProc)(const GUID* pcGuidDevice, LPDIRECTSOUNDCAPTURE *ppDSC8, LPUNKNOWN pUnkOuter); -typedef HRESULT (WINAPI * mal_DirectSoundCaptureEnumerateAProc)(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext); +// mini_al only uses priority or exclusive modes. +#define MAL_DSSCL_NORMAL 1 +#define MAL_DSSCL_PRIORITY 2 +#define MAL_DSSCL_EXCLUSIVE 3 +#define MAL_DSSCL_WRITEPRIMARY 4 -static HMODULE mal_open_dsound_dll() +#define MAL_DSCAPS_PRIMARYMONO 0x00000001 +#define MAL_DSCAPS_PRIMARYSTEREO 0x00000002 +#define MAL_DSCAPS_PRIMARY8BIT 0x00000004 +#define MAL_DSCAPS_PRIMARY16BIT 0x00000008 +#define MAL_DSCAPS_CONTINUOUSRATE 0x00000010 +#define MAL_DSCAPS_EMULDRIVER 0x00000020 +#define MAL_DSCAPS_CERTIFIED 0x00000040 +#define MAL_DSCAPS_SECONDARYMONO 0x00000100 +#define MAL_DSCAPS_SECONDARYSTEREO 0x00000200 +#define MAL_DSCAPS_SECONDARY8BIT 0x00000400 +#define MAL_DSCAPS_SECONDARY16BIT 0x00000800 + +#define MAL_DSBCAPS_PRIMARYBUFFER 0x00000001 +#define MAL_DSBCAPS_STATIC 0x00000002 +#define MAL_DSBCAPS_LOCHARDWARE 0x00000004 +#define MAL_DSBCAPS_LOCSOFTWARE 0x00000008 +#define MAL_DSBCAPS_CTRL3D 0x00000010 +#define MAL_DSBCAPS_CTRLFREQUENCY 0x00000020 +#define MAL_DSBCAPS_CTRLPAN 0x00000040 +#define MAL_DSBCAPS_CTRLVOLUME 0x00000080 +#define MAL_DSBCAPS_CTRLPOSITIONNOTIFY 0x00000100 +#define MAL_DSBCAPS_CTRLFX 0x00000200 +#define MAL_DSBCAPS_STICKYFOCUS 0x00004000 +#define MAL_DSBCAPS_GLOBALFOCUS 0x00008000 +#define MAL_DSBCAPS_GETCURRENTPOSITION2 0x00010000 +#define MAL_DSBCAPS_MUTE3DATMAXDISTANCE 0x00020000 +#define MAL_DSBCAPS_LOCDEFER 0x00040000 +#define MAL_DSBCAPS_TRUEPLAYPOSITION 0x00080000 + +#define MAL_DSBPLAY_LOOPING 0x00000001 +#define MAL_DSBPLAY_LOCHARDWARE 0x00000002 +#define MAL_DSBPLAY_LOCSOFTWARE 0x00000004 +#define MAL_DSBPLAY_TERMINATEBY_TIME 0x00000008 +#define MAL_DSBPLAY_TERMINATEBY_DISTANCE 0x00000010 +#define MAL_DSBPLAY_TERMINATEBY_PRIORITY 0x00000020 + +#define MAL_DSCBSTART_LOOPING 0x00000001 + +typedef struct { - return LoadLibraryW(L"dsound.dll"); + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwReserved; + WAVEFORMATEX* lpwfxFormat; + GUID guid3DAlgorithm; +} MAL_DSBUFFERDESC; + +typedef struct +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwReserved; + WAVEFORMATEX* lpwfxFormat; + DWORD dwFXCount; + void* lpDSCFXDesc; // <-- mini_al doesn't use this, so set to void*. +} MAL_DSCBUFFERDESC; + +typedef struct +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwMinSecondarySampleRate; + DWORD dwMaxSecondarySampleRate; + DWORD dwPrimaryBuffers; + DWORD dwMaxHwMixingAllBuffers; + DWORD dwMaxHwMixingStaticBuffers; + DWORD dwMaxHwMixingStreamingBuffers; + DWORD dwFreeHwMixingAllBuffers; + DWORD dwFreeHwMixingStaticBuffers; + DWORD dwFreeHwMixingStreamingBuffers; + DWORD dwMaxHw3DAllBuffers; + DWORD dwMaxHw3DStaticBuffers; + DWORD dwMaxHw3DStreamingBuffers; + DWORD dwFreeHw3DAllBuffers; + DWORD dwFreeHw3DStaticBuffers; + DWORD dwFreeHw3DStreamingBuffers; + DWORD dwTotalHwMemBytes; + DWORD dwFreeHwMemBytes; + DWORD dwMaxContigFreeHwMemBytes; + DWORD dwUnlockTransferRateHwBuffers; + DWORD dwPlayCpuOverheadSwBuffers; + DWORD dwReserved1; + DWORD dwReserved2; +} MAL_DSCAPS; + +typedef struct +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwUnlockTransferRate; + DWORD dwPlayCpuOverhead; +} MAL_DSBCAPS; + +typedef struct +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwFormats; + DWORD dwChannels; +} MAL_DSCCAPS; + +typedef struct +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwReserved; +} MAL_DSCBCAPS; + +typedef struct +{ + DWORD dwOffset; + HANDLE hEventNotify; +} MAL_DSBPOSITIONNOTIFY; + +typedef struct mal_IDirectSound mal_IDirectSound; +typedef struct mal_IDirectSoundBuffer mal_IDirectSoundBuffer; +typedef struct mal_IDirectSoundCapture mal_IDirectSoundCapture; +typedef struct mal_IDirectSoundCaptureBuffer mal_IDirectSoundCaptureBuffer; +typedef struct mal_IDirectSoundNotify mal_IDirectSoundNotify; + + +// COM objects. The way these work is that you have a vtable (a list of function pointers, kind of +// like how C++ works internally), and then you have a structure with a single member, which is a +// pointer to the vtable. The vtable is where the methods of the object are defined. Methods need +// to be in a specific order, and parent classes need to have their methods declared first. + +// IDirectSound +typedef struct +{ + // IUnknown + HRESULT (STDMETHODCALLTYPE * QueryInterface)(mal_IDirectSound* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (mal_IDirectSound* pThis); + ULONG (STDMETHODCALLTYPE * Release) (mal_IDirectSound* pThis); + + // IDirectSound + HRESULT (STDMETHODCALLTYPE * CreateSoundBuffer) (mal_IDirectSound* pThis, const MAL_DSBUFFERDESC* pDSBufferDesc, mal_IDirectSoundBuffer** ppDSBuffer, void* pUnkOuter); + HRESULT (STDMETHODCALLTYPE * GetCaps) (mal_IDirectSound* pThis, MAL_DSCAPS* pDSCaps); + HRESULT (STDMETHODCALLTYPE * DuplicateSoundBuffer)(mal_IDirectSound* pThis, mal_IDirectSoundBuffer* pDSBufferOriginal, mal_IDirectSoundBuffer** ppDSBufferDuplicate); + HRESULT (STDMETHODCALLTYPE * SetCooperativeLevel) (mal_IDirectSound* pThis, HWND hwnd, DWORD dwLevel); + HRESULT (STDMETHODCALLTYPE * Compact) (mal_IDirectSound* pThis); + HRESULT (STDMETHODCALLTYPE * GetSpeakerConfig) (mal_IDirectSound* pThis, DWORD* pSpeakerConfig); + HRESULT (STDMETHODCALLTYPE * SetSpeakerConfig) (mal_IDirectSound* pThis, DWORD dwSpeakerConfig); + HRESULT (STDMETHODCALLTYPE * Initialize) (mal_IDirectSound* pThis, const GUID* pGuidDevice); +} mal_IDirectSoundVtbl; +struct mal_IDirectSound +{ + mal_IDirectSoundVtbl* lpVtbl; +}; +HRESULT mal_IDirectSound_QueryInterface(mal_IDirectSound* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG mal_IDirectSound_AddRef(mal_IDirectSound* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG mal_IDirectSound_Release(mal_IDirectSound* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT mal_IDirectSound_CreateSoundBuffer(mal_IDirectSound* pThis, const MAL_DSBUFFERDESC* pDSBufferDesc, mal_IDirectSoundBuffer** ppDSBuffer, void* pUnkOuter) { return pThis->lpVtbl->CreateSoundBuffer(pThis, pDSBufferDesc, ppDSBuffer, pUnkOuter); } +HRESULT mal_IDirectSound_GetCaps(mal_IDirectSound* pThis, MAL_DSCAPS* pDSCaps) { return pThis->lpVtbl->GetCaps(pThis, pDSCaps); } +HRESULT mal_IDirectSound_DuplicateSoundBuffer(mal_IDirectSound* pThis, mal_IDirectSoundBuffer* pDSBufferOriginal, mal_IDirectSoundBuffer** ppDSBufferDuplicate) { return pThis->lpVtbl->DuplicateSoundBuffer(pThis, pDSBufferOriginal, ppDSBufferDuplicate); } +HRESULT mal_IDirectSound_SetCooperativeLevel(mal_IDirectSound* pThis, HWND hwnd, DWORD dwLevel) { return pThis->lpVtbl->SetCooperativeLevel(pThis, hwnd, dwLevel); } +HRESULT mal_IDirectSound_Compact(mal_IDirectSound* pThis) { return pThis->lpVtbl->Compact(pThis); } +HRESULT mal_IDirectSound_GetSpeakerConfig(mal_IDirectSound* pThis, DWORD* pSpeakerConfig) { return pThis->lpVtbl->GetSpeakerConfig(pThis, pSpeakerConfig); } +HRESULT mal_IDirectSound_SetSpeakerConfig(mal_IDirectSound* pThis, DWORD dwSpeakerConfig) { return pThis->lpVtbl->SetSpeakerConfig(pThis, dwSpeakerConfig); } +HRESULT mal_IDirectSound_Initialize(mal_IDirectSound* pThis, const GUID* pGuidDevice) { return pThis->lpVtbl->Initialize(pThis, pGuidDevice); } + + +// IDirectSoundBuffer +typedef struct +{ + // IUnknown + HRESULT (STDMETHODCALLTYPE * QueryInterface)(mal_IDirectSoundBuffer* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (mal_IDirectSoundBuffer* pThis); + ULONG (STDMETHODCALLTYPE * Release) (mal_IDirectSoundBuffer* pThis); + + // IDirectSoundBuffer + HRESULT (STDMETHODCALLTYPE * GetCaps) (mal_IDirectSoundBuffer* pThis, MAL_DSBCAPS* pDSBufferCaps); + HRESULT (STDMETHODCALLTYPE * GetCurrentPosition)(mal_IDirectSoundBuffer* pThis, DWORD* pCurrentPlayCursor, DWORD* pCurrentWriteCursor); + HRESULT (STDMETHODCALLTYPE * GetFormat) (mal_IDirectSoundBuffer* pThis, WAVEFORMATEX* pFormat, DWORD dwSizeAllocated, DWORD* pSizeWritten); + HRESULT (STDMETHODCALLTYPE * GetVolume) (mal_IDirectSoundBuffer* pThis, LONG* pVolume); + HRESULT (STDMETHODCALLTYPE * GetPan) (mal_IDirectSoundBuffer* pThis, LONG* pPan); + HRESULT (STDMETHODCALLTYPE * GetFrequency) (mal_IDirectSoundBuffer* pThis, DWORD* pFrequency); + HRESULT (STDMETHODCALLTYPE * GetStatus) (mal_IDirectSoundBuffer* pThis, DWORD* pStatus); + HRESULT (STDMETHODCALLTYPE * Initialize) (mal_IDirectSoundBuffer* pThis, mal_IDirectSound* pDirectSound, const MAL_DSBUFFERDESC* pDSBufferDesc); + HRESULT (STDMETHODCALLTYPE * Lock) (mal_IDirectSoundBuffer* pThis, DWORD dwOffset, DWORD dwBytes, void** ppAudioPtr1, DWORD* pAudioBytes1, void** ppAudioPtr2, DWORD* pAudioBytes2, DWORD dwFlags); + HRESULT (STDMETHODCALLTYPE * Play) (mal_IDirectSoundBuffer* pThis, DWORD dwReserved1, DWORD dwPriority, DWORD dwFlags); + HRESULT (STDMETHODCALLTYPE * SetCurrentPosition)(mal_IDirectSoundBuffer* pThis, DWORD dwNewPosition); + HRESULT (STDMETHODCALLTYPE * SetFormat) (mal_IDirectSoundBuffer* pThis, const WAVEFORMATEX* pFormat); + HRESULT (STDMETHODCALLTYPE * SetVolume) (mal_IDirectSoundBuffer* pThis, LONG volume); + HRESULT (STDMETHODCALLTYPE * SetPan) (mal_IDirectSoundBuffer* pThis, LONG pan); + HRESULT (STDMETHODCALLTYPE * SetFrequency) (mal_IDirectSoundBuffer* pThis, DWORD dwFrequency); + HRESULT (STDMETHODCALLTYPE * Stop) (mal_IDirectSoundBuffer* pThis); + HRESULT (STDMETHODCALLTYPE * Unlock) (mal_IDirectSoundBuffer* pThis, void* pAudioPtr1, DWORD dwAudioBytes1, void* pAudioPtr2, DWORD dwAudioBytes2); + HRESULT (STDMETHODCALLTYPE * Restore) (mal_IDirectSoundBuffer* pThis); +} mal_IDirectSoundBufferVtbl; +struct mal_IDirectSoundBuffer +{ + mal_IDirectSoundBufferVtbl* lpVtbl; +}; +HRESULT mal_IDirectSoundBuffer_QueryInterface(mal_IDirectSoundBuffer* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG mal_IDirectSoundBuffer_AddRef(mal_IDirectSoundBuffer* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG mal_IDirectSoundBuffer_Release(mal_IDirectSoundBuffer* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT mal_IDirectSoundBuffer_GetCaps(mal_IDirectSoundBuffer* pThis, MAL_DSBCAPS* pDSBufferCaps) { return pThis->lpVtbl->GetCaps(pThis, pDSBufferCaps); } +HRESULT mal_IDirectSoundBuffer_GetCurrentPosition(mal_IDirectSoundBuffer* pThis, DWORD* pCurrentPlayCursor, DWORD* pCurrentWriteCursor) { return pThis->lpVtbl->GetCurrentPosition(pThis, pCurrentPlayCursor, pCurrentWriteCursor); } +HRESULT mal_IDirectSoundBuffer_GetFormat(mal_IDirectSoundBuffer* pThis, WAVEFORMATEX* pFormat, DWORD dwSizeAllocated, DWORD* pSizeWritten) { return pThis->lpVtbl->GetFormat(pThis, pFormat, dwSizeAllocated, pSizeWritten); } +HRESULT mal_IDirectSoundBuffer_GetVolume(mal_IDirectSoundBuffer* pThis, LONG* pVolume) { return pThis->lpVtbl->GetVolume(pThis, pVolume); } +HRESULT mal_IDirectSoundBuffer_GetPan(mal_IDirectSoundBuffer* pThis, LONG* pPan) { return pThis->lpVtbl->GetPan(pThis, pPan); } +HRESULT mal_IDirectSoundBuffer_GetFrequency(mal_IDirectSoundBuffer* pThis, DWORD* pFrequency) { return pThis->lpVtbl->GetFrequency(pThis, pFrequency); } +HRESULT mal_IDirectSoundBuffer_GetStatus(mal_IDirectSoundBuffer* pThis, DWORD* pStatus) { return pThis->lpVtbl->GetStatus(pThis, pStatus); } +HRESULT mal_IDirectSoundBuffer_Initialize(mal_IDirectSoundBuffer* pThis, mal_IDirectSound* pDirectSound, const MAL_DSBUFFERDESC* pDSBufferDesc) { return pThis->lpVtbl->Initialize(pThis, pDirectSound, pDSBufferDesc); } +HRESULT mal_IDirectSoundBuffer_Lock(mal_IDirectSoundBuffer* pThis, DWORD dwOffset, DWORD dwBytes, void** ppAudioPtr1, DWORD* pAudioBytes1, void** ppAudioPtr2, DWORD* pAudioBytes2, DWORD dwFlags) { return pThis->lpVtbl->Lock(pThis, dwOffset, dwBytes, ppAudioPtr1, pAudioBytes1, ppAudioPtr2, pAudioBytes2, dwFlags); } +HRESULT mal_IDirectSoundBuffer_Play(mal_IDirectSoundBuffer* pThis, DWORD dwReserved1, DWORD dwPriority, DWORD dwFlags) { return pThis->lpVtbl->Play(pThis, dwReserved1, dwPriority, dwFlags); } +HRESULT mal_IDirectSoundBuffer_SetCurrentPosition(mal_IDirectSoundBuffer* pThis, DWORD dwNewPosition) { return pThis->lpVtbl->SetCurrentPosition(pThis, dwNewPosition); } +HRESULT mal_IDirectSoundBuffer_SetFormat(mal_IDirectSoundBuffer* pThis, const WAVEFORMATEX* pFormat) { return pThis->lpVtbl->SetFormat(pThis, pFormat); } +HRESULT mal_IDirectSoundBuffer_SetVolume(mal_IDirectSoundBuffer* pThis, LONG volume) { return pThis->lpVtbl->SetVolume(pThis, volume); } +HRESULT mal_IDirectSoundBuffer_SetPan(mal_IDirectSoundBuffer* pThis, LONG pan) { return pThis->lpVtbl->SetPan(pThis, pan); } +HRESULT mal_IDirectSoundBuffer_SetFrequency(mal_IDirectSoundBuffer* pThis, DWORD dwFrequency) { return pThis->lpVtbl->SetFrequency(pThis, dwFrequency); } +HRESULT mal_IDirectSoundBuffer_Stop(mal_IDirectSoundBuffer* pThis) { return pThis->lpVtbl->Stop(pThis); } +HRESULT mal_IDirectSoundBuffer_Unlock(mal_IDirectSoundBuffer* pThis, void* pAudioPtr1, DWORD dwAudioBytes1, void* pAudioPtr2, DWORD dwAudioBytes2) { return pThis->lpVtbl->Unlock(pThis, pAudioPtr1, dwAudioBytes1, pAudioPtr2, dwAudioBytes2); } +HRESULT mal_IDirectSoundBuffer_Restore(mal_IDirectSoundBuffer* pThis) { return pThis->lpVtbl->Restore(pThis); } + + +// IDirectSoundCapture +typedef struct +{ + // IUnknown + HRESULT (STDMETHODCALLTYPE * QueryInterface)(mal_IDirectSoundCapture* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (mal_IDirectSoundCapture* pThis); + ULONG (STDMETHODCALLTYPE * Release) (mal_IDirectSoundCapture* pThis); + + // IDirectSoundCapture + HRESULT (STDMETHODCALLTYPE * CreateCaptureBuffer)(mal_IDirectSoundCapture* pThis, const MAL_DSCBUFFERDESC* pDSCBufferDesc, mal_IDirectSoundCaptureBuffer** ppDSCBuffer, void* pUnkOuter); + HRESULT (STDMETHODCALLTYPE * GetCaps) (mal_IDirectSoundCapture* pThis, MAL_DSCCAPS* pDSCCaps); + HRESULT (STDMETHODCALLTYPE * Initialize) (mal_IDirectSoundCapture* pThis, const GUID* pGuidDevice); +} mal_IDirectSoundCaptureVtbl; +struct mal_IDirectSoundCapture +{ + mal_IDirectSoundCaptureVtbl* lpVtbl; +}; +HRESULT mal_IDirectSoundCapture_QueryInterface(mal_IDirectSoundCapture* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG mal_IDirectSoundCapture_AddRef(mal_IDirectSoundCapture* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG mal_IDirectSoundCapture_Release(mal_IDirectSoundCapture* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT mal_IDirectSoundCapture_CreateCaptureBuffer(mal_IDirectSoundCapture* pThis, const MAL_DSCBUFFERDESC* pDSCBufferDesc, mal_IDirectSoundCaptureBuffer** ppDSCBuffer, void* pUnkOuter) { return pThis->lpVtbl->CreateCaptureBuffer(pThis, pDSCBufferDesc, ppDSCBuffer, pUnkOuter); } +HRESULT mal_IDirectSoundCapture_GetCaps (mal_IDirectSoundCapture* pThis, MAL_DSCCAPS* pDSCCaps) { return pThis->lpVtbl->GetCaps(pThis, pDSCCaps); } +HRESULT mal_IDirectSoundCapture_Initialize (mal_IDirectSoundCapture* pThis, const GUID* pGuidDevice) { return pThis->lpVtbl->Initialize(pThis, pGuidDevice); } + + +// IDirectSoundCaptureBuffer +typedef struct +{ + // IUnknown + HRESULT (STDMETHODCALLTYPE * QueryInterface)(mal_IDirectSoundCaptureBuffer* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (mal_IDirectSoundCaptureBuffer* pThis); + ULONG (STDMETHODCALLTYPE * Release) (mal_IDirectSoundCaptureBuffer* pThis); + + // IDirectSoundCaptureBuffer + HRESULT (STDMETHODCALLTYPE * GetCaps) (mal_IDirectSoundCaptureBuffer* pThis, MAL_DSCBCAPS* pDSCBCaps); + HRESULT (STDMETHODCALLTYPE * GetCurrentPosition)(mal_IDirectSoundCaptureBuffer* pThis, DWORD* pCapturePosition, DWORD* pReadPosition); + HRESULT (STDMETHODCALLTYPE * GetFormat) (mal_IDirectSoundCaptureBuffer* pThis, WAVEFORMATEX* pFormat, DWORD dwSizeAllocated, DWORD* pSizeWritten); + HRESULT (STDMETHODCALLTYPE * GetStatus) (mal_IDirectSoundCaptureBuffer* pThis, DWORD* pStatus); + HRESULT (STDMETHODCALLTYPE * Initialize) (mal_IDirectSoundCaptureBuffer* pThis, mal_IDirectSoundCapture* pDirectSoundCapture, const MAL_DSCBUFFERDESC* pDSCBufferDesc); + HRESULT (STDMETHODCALLTYPE * Lock) (mal_IDirectSoundCaptureBuffer* pThis, DWORD dwOffset, DWORD dwBytes, void** ppAudioPtr1, DWORD* pAudioBytes1, void** ppAudioPtr2, DWORD* pAudioBytes2, DWORD dwFlags); + HRESULT (STDMETHODCALLTYPE * Start) (mal_IDirectSoundCaptureBuffer* pThis, DWORD dwFlags); + HRESULT (STDMETHODCALLTYPE * Stop) (mal_IDirectSoundCaptureBuffer* pThis); + HRESULT (STDMETHODCALLTYPE * Unlock) (mal_IDirectSoundCaptureBuffer* pThis, void* pAudioPtr1, DWORD dwAudioBytes1, void* pAudioPtr2, DWORD dwAudioBytes2); +} mal_IDirectSoundCaptureBufferVtbl; +struct mal_IDirectSoundCaptureBuffer +{ + mal_IDirectSoundCaptureBufferVtbl* lpVtbl; +}; +HRESULT mal_IDirectSoundCaptureBuffer_QueryInterface(mal_IDirectSoundCaptureBuffer* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG mal_IDirectSoundCaptureBuffer_AddRef(mal_IDirectSoundCaptureBuffer* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG mal_IDirectSoundCaptureBuffer_Release(mal_IDirectSoundCaptureBuffer* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT mal_IDirectSoundCaptureBuffer_GetCaps(mal_IDirectSoundCaptureBuffer* pThis, MAL_DSCBCAPS* pDSCBCaps) { return pThis->lpVtbl->GetCaps(pThis, pDSCBCaps); } +HRESULT mal_IDirectSoundCaptureBuffer_GetCurrentPosition(mal_IDirectSoundCaptureBuffer* pThis, DWORD* pCapturePosition, DWORD* pReadPosition) { return pThis->lpVtbl->GetCurrentPosition(pThis, pCapturePosition, pReadPosition); } +HRESULT mal_IDirectSoundCaptureBuffer_GetFormat(mal_IDirectSoundCaptureBuffer* pThis, WAVEFORMATEX* pFormat, DWORD dwSizeAllocated, DWORD* pSizeWritten) { return pThis->lpVtbl->GetFormat(pThis, pFormat, dwSizeAllocated, pSizeWritten); } +HRESULT mal_IDirectSoundCaptureBuffer_GetStatus(mal_IDirectSoundCaptureBuffer* pThis, DWORD* pStatus) { return pThis->lpVtbl->GetStatus(pThis, pStatus); } +HRESULT mal_IDirectSoundCaptureBuffer_Initialize(mal_IDirectSoundCaptureBuffer* pThis, mal_IDirectSoundCapture* pDirectSoundCapture, const MAL_DSCBUFFERDESC* pDSCBufferDesc) { return pThis->lpVtbl->Initialize(pThis, pDirectSoundCapture, pDSCBufferDesc); } +HRESULT mal_IDirectSoundCaptureBuffer_Lock(mal_IDirectSoundCaptureBuffer* pThis, DWORD dwOffset, DWORD dwBytes, void** ppAudioPtr1, DWORD* pAudioBytes1, void** ppAudioPtr2, DWORD* pAudioBytes2, DWORD dwFlags) { return pThis->lpVtbl->Lock(pThis, dwOffset, dwBytes, ppAudioPtr1, pAudioBytes1, ppAudioPtr2, pAudioBytes2, dwFlags); } +HRESULT mal_IDirectSoundCaptureBuffer_Start(mal_IDirectSoundCaptureBuffer* pThis, DWORD dwFlags) { return pThis->lpVtbl->Start(pThis, dwFlags); } +HRESULT mal_IDirectSoundCaptureBuffer_Stop(mal_IDirectSoundCaptureBuffer* pThis) { return pThis->lpVtbl->Stop(pThis); } +HRESULT mal_IDirectSoundCaptureBuffer_Unlock(mal_IDirectSoundCaptureBuffer* pThis, void* pAudioPtr1, DWORD dwAudioBytes1, void* pAudioPtr2, DWORD dwAudioBytes2) { return pThis->lpVtbl->Unlock(pThis, pAudioPtr1, dwAudioBytes1, pAudioPtr2, dwAudioBytes2); } + + +// IDirectSoundNotify +typedef struct +{ + // IUnknown + HRESULT (STDMETHODCALLTYPE * QueryInterface)(mal_IDirectSoundNotify* pThis, const IID* const riid, void** ppObject); + ULONG (STDMETHODCALLTYPE * AddRef) (mal_IDirectSoundNotify* pThis); + ULONG (STDMETHODCALLTYPE * Release) (mal_IDirectSoundNotify* pThis); + + // IDirectSoundNotify + HRESULT (STDMETHODCALLTYPE * SetNotificationPositions)(mal_IDirectSoundNotify* pThis, DWORD dwPositionNotifies, const MAL_DSBPOSITIONNOTIFY* pPositionNotifies); +} mal_IDirectSoundNotifyVtbl; +struct mal_IDirectSoundNotify +{ + mal_IDirectSoundNotifyVtbl* lpVtbl; +}; +HRESULT mal_IDirectSoundNotify_QueryInterface(mal_IDirectSoundNotify* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +ULONG mal_IDirectSoundNotify_AddRef(mal_IDirectSoundNotify* pThis) { return pThis->lpVtbl->AddRef(pThis); } +ULONG mal_IDirectSoundNotify_Release(mal_IDirectSoundNotify* pThis) { return pThis->lpVtbl->Release(pThis); } +HRESULT mal_IDirectSoundNotify_SetNotificationPositions(mal_IDirectSoundNotify* pThis, DWORD dwPositionNotifies, const MAL_DSBPOSITIONNOTIFY* pPositionNotifies) { return pThis->lpVtbl->SetNotificationPositions(pThis, dwPositionNotifies, pPositionNotifies); } + + +typedef BOOL (CALLBACK * mal_DSEnumCallbackAProc) (LPGUID pDeviceGUID, LPCSTR pDeviceDescription, LPCSTR pModule, LPVOID pContext); +typedef HRESULT (WINAPI * mal_DirectSoundCreateProc) (const GUID* pcGuidDevice, mal_IDirectSound** ppDS8, LPUNKNOWN pUnkOuter); +typedef HRESULT (WINAPI * mal_DirectSoundEnumerateAProc) (mal_DSEnumCallbackAProc pDSEnumCallback, LPVOID pContext); +typedef HRESULT (WINAPI * mal_DirectSoundCaptureCreateProc) (const GUID* pcGuidDevice, mal_IDirectSoundCapture** ppDSC8, LPUNKNOWN pUnkOuter); +typedef HRESULT (WINAPI * mal_DirectSoundCaptureEnumerateAProc)(mal_DSEnumCallbackAProc pDSEnumCallback, LPVOID pContext); + + +// Retrieves the channel count and channel map for the given speaker configuration. If the speaker configuration is unknown, +// the channel count and channel map will be left unmodified. +void mal_get_channels_from_speaker_config__dsound(DWORD speakerConfig, WORD* pChannelsOut, DWORD* pChannelMapOut) +{ + WORD channels = 0; + if (pChannelsOut != NULL) { + channels = *pChannelsOut; + } + + DWORD channelMap = 0; + if (pChannelMapOut != NULL) { + channelMap = *pChannelMapOut; + } + + // The speaker configuration is a combination of speaker config and speaker geometry. The lower 8 bits is what we care about. The upper + // 16 bits is for the geometry. + switch ((BYTE)(speakerConfig)) { + case 1 /*DSSPEAKER_HEADPHONE*/: channels = 2; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; break; + case 2 /*DSSPEAKER_MONO*/: channels = 1; channelMap = SPEAKER_FRONT_CENTER; break; + case 3 /*DSSPEAKER_QUAD*/: channels = 4; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break; + case 4 /*DSSPEAKER_STEREO*/: channels = 2; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; break; + case 5 /*DSSPEAKER_SURROUND*/: channels = 4; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER; break; + case 6 /*DSSPEAKER_5POINT1_BACK*/ /*DSSPEAKER_5POINT1*/: channels = 6; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break; + case 7 /*DSSPEAKER_7POINT1_WIDE*/ /*DSSPEAKER_7POINT1*/: channels = 8; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER; break; + case 8 /*DSSPEAKER_7POINT1_SURROUND*/: channels = 8; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT; break; + case 9 /*DSSPEAKER_5POINT1_SURROUND*/: channels = 6; channelMap = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT; break; + default: break; + } + + if (pChannelsOut != NULL) { + *pChannelsOut = channels; + } + + if (pChannelMapOut != NULL) { + *pChannelMapOut = channelMap; + } } -static void mal_close_dsound_dll(HMODULE hModule) + +mal_bool32 mal_context_is_device_id_equal__dsound(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) { - FreeLibrary(hModule); + mal_assert(pContext != NULL); + mal_assert(pID0 != NULL); + mal_assert(pID1 != NULL); + (void)pContext; + + return memcmp(pID0->dsound, pID1->dsound, sizeof(pID0->dsound)) == 0; } +typedef struct +{ + mal_context* pContext; + mal_device_type deviceType; + mal_enum_devices_callback_proc callback; + void* pUserData; + mal_bool32 terminated; +} mal_context_enumerate_devices_callback_data__dsound; + +BOOL CALLBACK mal_context_enumerate_devices_callback__dsound(LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext) +{ + (void)lpcstrModule; + + mal_context_enumerate_devices_callback_data__dsound* pData = (mal_context_enumerate_devices_callback_data__dsound*)lpContext; + mal_assert(pData != NULL); + + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + + // ID. + if (lpGuid != NULL) { + mal_copy_memory(deviceInfo.id.dsound, lpGuid, 16); + } else { + mal_zero_memory(deviceInfo.id.dsound, 16); + } + + // Name / Description + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), lpcstrDescription, (size_t)-1); + + + // Call the callback function, but make sure we stop enumerating if the callee requested so. + pData->terminated = !pData->callback(pData->pContext, pData->deviceType, &deviceInfo, pData->pUserData); + if (pData->terminated) { + return FALSE; // Stop enumeration. + } else { + return TRUE; // Continue enumeration. + } +} + +mal_result mal_context_enumerate_devices__dsound(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +{ + mal_assert(pContext != NULL); + mal_assert(callback != NULL); + + mal_context_enumerate_devices_callback_data__dsound data; + data.pContext = pContext; + data.callback = callback; + data.pUserData = pUserData; + data.terminated = MAL_FALSE; + + // Playback. + if (!data.terminated) { + data.deviceType = mal_device_type_playback; + ((mal_DirectSoundEnumerateAProc)pContext->dsound.DirectSoundEnumerateA)(mal_context_enumerate_devices_callback__dsound, &data); + } + + // Capture. + if (!data.terminated) { + data.deviceType = mal_device_type_capture; + ((mal_DirectSoundCaptureEnumerateAProc)pContext->dsound.DirectSoundCaptureEnumerateA)(mal_context_enumerate_devices_callback__dsound, &data); + } + + return MAL_SUCCESS; +} + + +typedef struct +{ + const mal_device_id* pDeviceID; + mal_device_info* pDeviceInfo; + mal_bool32 found; +} mal_context_get_device_info_callback_data__dsound; + +BOOL CALLBACK mal_context_get_device_info_callback__dsound(LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext) +{ + (void)lpcstrModule; + + mal_context_get_device_info_callback_data__dsound* pData = (mal_context_get_device_info_callback_data__dsound*)lpContext; + mal_assert(pData != NULL); + + if ((pData->pDeviceID == NULL || mal_is_guid_equal(pData->pDeviceID->dsound, &MAL_GUID_NULL)) && (lpGuid == NULL || mal_is_guid_equal(lpGuid, &MAL_GUID_NULL))) { + // Default device. + mal_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), lpcstrDescription, (size_t)-1); + pData->found = MAL_TRUE; + return FALSE; // Stop enumeration. + } else { + // Not the default device. + if (lpGuid != NULL) { + if (memcmp(pData->pDeviceID->dsound, lpGuid, sizeof(pData->pDeviceID->dsound)) == 0) { + mal_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), lpcstrDescription, (size_t)-1); + pData->found = MAL_TRUE; + return FALSE; // Stop enumeration. + } + } + } + + return TRUE; +} + +mal_result mal_context_get_device_info__dsound(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +{ + (void)shareMode; + + if (pDeviceID != NULL) { + // ID. + mal_copy_memory(pDeviceInfo->id.dsound, pDeviceID->dsound, 16); + + // Name / Description. This is retrieved by enumerating over each device until we find that one that matches the input ID. + mal_context_get_device_info_callback_data__dsound data; + data.pDeviceID = pDeviceID; + data.pDeviceInfo = pDeviceInfo; + data.found = MAL_FALSE; + if (deviceType == mal_device_type_playback) { + ((mal_DirectSoundEnumerateAProc)pContext->dsound.DirectSoundEnumerateA)(mal_context_get_device_info_callback__dsound, &data); + } else { + ((mal_DirectSoundCaptureEnumerateAProc)pContext->dsound.DirectSoundCaptureEnumerateA)(mal_context_get_device_info_callback__dsound, &data); + } + + if (data.found) { + return MAL_SUCCESS; + } else { + return MAL_NO_DEVICE; + } + } else { + // I don't think there's a way to get the name of the default device with DirectSound. In this case we just need to use defaults. + + // ID + mal_zero_memory(pDeviceInfo->id.dsound, 16); + + // Name / Description/ + if (deviceType == mal_device_type_playback) { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } + } + + return MAL_SUCCESS; +} + mal_result mal_context_init__dsound(mal_context* pContext) { mal_assert(pContext != NULL); - (void)pContext; + pContext->dsound.hDSoundDLL = mal_dlopen("dsound.dll"); + if (pContext->dsound.hDSoundDLL == NULL) { + return MAL_API_NOT_FOUND; + } + + pContext->dsound.DirectSoundCreate = mal_dlsym(pContext->dsound.hDSoundDLL, "DirectSoundCreate"); + pContext->dsound.DirectSoundEnumerateA = mal_dlsym(pContext->dsound.hDSoundDLL, "DirectSoundEnumerateA"); + pContext->dsound.DirectSoundCaptureCreate = mal_dlsym(pContext->dsound.hDSoundDLL, "DirectSoundCaptureCreate"); + pContext->dsound.DirectSoundCaptureEnumerateA = mal_dlsym(pContext->dsound.hDSoundDLL, "DirectSoundCaptureEnumerateA"); + + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__dsound; + pContext->onEnumDevices = mal_context_enumerate_devices__dsound; + pContext->onGetDeviceInfo = mal_context_get_device_info__dsound; + return MAL_SUCCESS; } @@ -4207,7 +6075,8 @@ mal_result mal_context_uninit__dsound(mal_context* pContext) mal_assert(pContext != NULL); mal_assert(pContext->backend == mal_backend_dsound); - (void)pContext; + mal_dlclose(pContext->dsound.hDSoundDLL); + return MAL_SUCCESS; } @@ -4219,7 +6088,7 @@ typedef struct mal_device_info* pInfo; } mal_device_enum_data__dsound; -static BOOL CALLBACK mal_enum_devices_callback__dsound(LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext) +BOOL CALLBACK mal_enum_devices_callback__dsound(LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext) { (void)lpcstrModule; @@ -4248,101 +6117,48 @@ static BOOL CALLBACK mal_enum_devices_callback__dsound(LPGUID lpGuid, LPCSTR lpc return TRUE; } -static mal_result mal_enumerate_devices__dsound(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) -{ - (void)pContext; - - mal_uint32 infoSize = *pCount; - *pCount = 0; - - mal_device_enum_data__dsound enumData; - enumData.deviceCount = 0; - enumData.infoCount = infoSize; - enumData.pInfo = pInfo; - - HMODULE dsoundDLL = mal_open_dsound_dll(); - if (dsoundDLL == NULL) { - return MAL_NO_BACKEND; - } - - if (type == mal_device_type_playback) { - mal_DirectSoundEnumerateAProc pDirectSoundEnumerateA = (mal_DirectSoundEnumerateAProc)GetProcAddress(dsoundDLL, "DirectSoundEnumerateA"); - if (pDirectSoundEnumerateA) { - pDirectSoundEnumerateA(mal_enum_devices_callback__dsound, &enumData); - } - } else { - mal_DirectSoundCaptureEnumerateAProc pDirectSoundCaptureEnumerateA = (mal_DirectSoundCaptureEnumerateAProc)GetProcAddress(dsoundDLL, "DirectSoundCaptureEnumerateA"); - if (pDirectSoundCaptureEnumerateA) { - pDirectSoundCaptureEnumerateA(mal_enum_devices_callback__dsound, &enumData); - } - } - - - mal_close_dsound_dll(dsoundDLL); - - *pCount = enumData.deviceCount; - return MAL_SUCCESS; -} - -static void mal_device_uninit__dsound(mal_device* pDevice) +void mal_device_uninit__dsound(mal_device* pDevice) { mal_assert(pDevice != NULL); - if (pDevice->dsound.hDSoundDLL != NULL) { - if (pDevice->dsound.pNotify) { - IDirectSoundNotify_Release((LPDIRECTSOUNDNOTIFY)pDevice->dsound.pNotify); - } + if (pDevice->dsound.pNotify != NULL) { + mal_IDirectSoundNotify_Release((mal_IDirectSoundNotify*)pDevice->dsound.pNotify); + } - if (pDevice->dsound.hStopEvent) { - CloseHandle(pDevice->dsound.hStopEvent); - } - for (mal_uint32 i = 0; i < pDevice->periods; ++i) { - if (pDevice->dsound.pNotifyEvents[i]) { - CloseHandle(pDevice->dsound.pNotifyEvents[i]); - } + if (pDevice->dsound.hStopEvent) { + CloseHandle(pDevice->dsound.hStopEvent); + } + for (mal_uint32 i = 0; i < pDevice->periods; ++i) { + if (pDevice->dsound.pNotifyEvents[i]) { + CloseHandle(pDevice->dsound.pNotifyEvents[i]); } + } - if (pDevice->dsound.pCaptureBuffer) { - IDirectSoundCaptureBuffer_Release((LPDIRECTSOUNDBUFFER)pDevice->dsound.pCaptureBuffer); - } - if (pDevice->dsound.pCapture) { - IDirectSoundCapture_Release((LPDIRECTSOUNDCAPTURE)pDevice->dsound.pCapture); - } + if (pDevice->dsound.pCaptureBuffer != NULL) { + mal_IDirectSoundCaptureBuffer_Release((mal_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); + } + if (pDevice->dsound.pCapture != NULL) { + mal_IDirectSoundCapture_Release((mal_IDirectSoundCapture*)pDevice->dsound.pCapture); + } - if (pDevice->dsound.pPlaybackBuffer) { - IDirectSoundBuffer_Release((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer); - } - if (pDevice->dsound.pPlaybackPrimaryBuffer) { - IDirectSoundBuffer_Release((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackPrimaryBuffer); - } - if (pDevice->dsound.pPlayback != NULL) { - IDirectSound_Release((LPDIRECTSOUND)pDevice->dsound.pPlayback); - } - - mal_close_dsound_dll((HMODULE)pDevice->dsound.hDSoundDLL); + if (pDevice->dsound.pPlaybackBuffer != NULL) { + mal_IDirectSoundBuffer_Release((mal_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer); + } + if (pDevice->dsound.pPlaybackPrimaryBuffer != NULL) { + mal_IDirectSoundBuffer_Release((mal_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer); + } + if (pDevice->dsound.pPlayback != NULL) { + mal_IDirectSound_Release((mal_IDirectSound*)pDevice->dsound.pPlayback); } } -static mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) { (void)pContext; -#ifdef __cplusplus - GUID _MAL_GUID_IID_DirectSoundNotify = MAL_GUID_IID_DirectSoundNotify; - GUID _MAL_GUID_IID_IDirectSoundCaptureBuffer = MAL_GUID_IID_IDirectSoundCaptureBuffer; -#else - GUID* _MAL_GUID_IID_DirectSoundNotify = &MAL_GUID_IID_DirectSoundNotify; - GUID* _MAL_GUID_IID_IDirectSoundCaptureBuffer = &MAL_GUID_IID_IDirectSoundCaptureBuffer; -#endif - mal_assert(pDevice != NULL); mal_zero_object(&pDevice->dsound); - pDevice->dsound.hDSoundDLL = (mal_handle)mal_open_dsound_dll(); - if (pDevice->dsound.hDSoundDLL == NULL) { - return MAL_NO_BACKEND; - } - // Check that we have a valid format. GUID subformat; switch (pConfig->format) @@ -4350,6 +6166,7 @@ static mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type case mal_format_u8: case mal_format_s16: case mal_format_s24: + //case mal_format_s24_32: case mal_format_s32: { subformat = MAL_GUID_KSDATAFORMAT_SUBTYPE_PCM; @@ -4371,7 +6188,7 @@ static mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type wf.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; wf.Format.nChannels = (WORD)pConfig->channels; wf.Format.nSamplesPerSec = (DWORD)pConfig->sampleRate; - wf.Format.wBitsPerSample = (WORD)mal_get_sample_size_in_bytes(pConfig->format)*8; + wf.Format.wBitsPerSample = (WORD)mal_get_bytes_per_sample(pConfig->format)*8; wf.Format.nBlockAlign = (wf.Format.nChannels * wf.Format.wBitsPerSample) / 8; wf.Format.nAvgBytesPerSec = wf.Format.nBlockAlign * wf.Format.nSamplesPerSec; wf.Samples.wValidBitsPerSample = wf.Format.wBitsPerSample; @@ -4382,15 +6199,9 @@ static mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type // Unfortunately DirectSound uses different APIs and data structures for playback and catpure devices :( if (type == mal_device_type_playback) { - mal_DirectSoundCreateProc pDirectSoundCreate = (mal_DirectSoundCreateProc)GetProcAddress((HMODULE)pDevice->dsound.hDSoundDLL, "DirectSoundCreate"); - if (pDirectSoundCreate == NULL) { + if (FAILED(((mal_DirectSoundCreateProc)pContext->dsound.DirectSoundCreate)((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, (mal_IDirectSound**)&pDevice->dsound.pPlayback, NULL))) { mal_device_uninit__dsound(pDevice); - return mal_post_error(pDevice, "[DirectSound] Could not find DirectSoundCreate().", MAL_API_NOT_FOUND); - } - - if (FAILED(pDirectSoundCreate((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, (LPDIRECTSOUND*)&pDevice->dsound.pPlayback, NULL))) { - mal_device_uninit__dsound(pDevice); - return mal_post_error(pDevice, "[DirectSound] DirectSoundCreate() failed for playback device.", MAL_DSOUND_FAILED_TO_CREATE_DEVICE); + return mal_post_error(pDevice, "[DirectSound] DirectSoundCreate() failed for playback device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } // The cooperative level must be set before doing anything else. @@ -4398,50 +6209,89 @@ static mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type if (hWnd == NULL) { hWnd = ((MAL_PFN_GetDesktopWindow)pContext->win32.GetDesktopWindow)(); } - if (FAILED(IDirectSound_SetCooperativeLevel((LPDIRECTSOUND)pDevice->dsound.pPlayback, hWnd, (pConfig->preferExclusiveMode) ? DSSCL_EXCLUSIVE : DSSCL_PRIORITY))) { + if (FAILED(mal_IDirectSound_SetCooperativeLevel((mal_IDirectSound*)pDevice->dsound.pPlayback, hWnd, (pConfig->shareMode == mal_share_mode_exclusive) ? MAL_DSSCL_EXCLUSIVE : MAL_DSSCL_PRIORITY))) { mal_device_uninit__dsound(pDevice); - return mal_post_error(pDevice, "[DirectSound] IDirectSound_SetCooperateiveLevel() failed for playback device.", MAL_DSOUND_FAILED_TO_SET_COOP_LEVEL); + return mal_post_error(pDevice, "[DirectSound] IDirectSound_SetCooperateiveLevel() failed for playback device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } - DSBUFFERDESC descDSPrimary; + MAL_DSBUFFERDESC descDSPrimary; mal_zero_object(&descDSPrimary); - descDSPrimary.dwSize = sizeof(DSBUFFERDESC); - descDSPrimary.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME; - if (FAILED(IDirectSound_CreateSoundBuffer((LPDIRECTSOUND)pDevice->dsound.pPlayback, &descDSPrimary, (LPDIRECTSOUNDBUFFER*)&pDevice->dsound.pPlaybackPrimaryBuffer, NULL))) { + descDSPrimary.dwSize = sizeof(MAL_DSBUFFERDESC); + descDSPrimary.dwFlags = MAL_DSBCAPS_PRIMARYBUFFER | MAL_DSBCAPS_CTRLVOLUME; + if (FAILED(mal_IDirectSound_CreateSoundBuffer((mal_IDirectSound*)pDevice->dsound.pPlayback, &descDSPrimary, (mal_IDirectSoundBuffer**)&pDevice->dsound.pPlaybackPrimaryBuffer, NULL))) { mal_device_uninit__dsound(pDevice); - return mal_post_error(pDevice, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's primary buffer.", MAL_DSOUND_FAILED_TO_CREATE_BUFFER); + return mal_post_error(pDevice, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's primary buffer.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } + + // We may want to make some adjustments to the format if we are using defaults. + MAL_DSCAPS caps; + mal_zero_object(&caps); + caps.dwSize = sizeof(caps); + if (FAILED(mal_IDirectSound_GetCaps((mal_IDirectSound*)pDevice->dsound.pPlayback, &caps))) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] IDirectSound_GetCaps() failed for playback device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (pDevice->usingDefaultChannels) { + if ((caps.dwFlags & MAL_DSCAPS_PRIMARYSTEREO) != 0) { + // It supports at least stereo, but could support more. + wf.Format.nChannels = 2; + + // Look at the speaker configuration to get a better idea on the channel count. + DWORD speakerConfig; + if (SUCCEEDED(mal_IDirectSound_GetSpeakerConfig((mal_IDirectSound*)pDevice->dsound.pPlayback, &speakerConfig))) { + mal_get_channels_from_speaker_config__dsound(speakerConfig, &wf.Format.nChannels, &wf.dwChannelMask); + } + } else { + // It does not support stereo, which means we are stuck with mono. + wf.Format.nChannels = 1; + } + } + + if (pDevice->usingDefaultSampleRate) { + // We base the sample rate on the values returned by GetCaps(). + if ((caps.dwFlags & MAL_DSCAPS_CONTINUOUSRATE) != 0) { + wf.Format.nSamplesPerSec = mal_get_best_sample_rate_within_range(caps.dwMinSecondarySampleRate, caps.dwMaxSecondarySampleRate); + } else { + wf.Format.nSamplesPerSec = caps.dwMaxSecondarySampleRate; + } + } + + wf.Format.nBlockAlign = (wf.Format.nChannels * wf.Format.wBitsPerSample) / 8; + wf.Format.nAvgBytesPerSec = wf.Format.nBlockAlign * wf.Format.nSamplesPerSec; + // From MSDN: // // The method succeeds even if the hardware does not support the requested format; DirectSound sets the buffer to the closest // supported format. To determine whether this has happened, an application can call the GetFormat method for the primary buffer // and compare the result with the format that was requested with the SetFormat method. - if (FAILED(IDirectSoundBuffer_SetFormat((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)&wf))) { + if (FAILED(mal_IDirectSoundBuffer_SetFormat((mal_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)&wf))) { mal_device_uninit__dsound(pDevice); return mal_post_error(pDevice, "[DirectSound] Failed to set format of playback device's primary buffer.", MAL_FORMAT_NOT_SUPPORTED); } - // Get the _actual_ properties of the buffer. This is silly API design... - DWORD requiredSize; - if (FAILED(IDirectSoundBuffer_GetFormat((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackPrimaryBuffer, NULL, 0, &requiredSize))) { - mal_device_uninit__dsound(pDevice); - return mal_post_error(pDevice, "[DirectSound] Failed to retrieve the actual format of the playback device's primary buffer.", MAL_FORMAT_NOT_SUPPORTED); - } - + // Get the _actual_ properties of the buffer. char rawdata[1024]; WAVEFORMATEXTENSIBLE* pActualFormat = (WAVEFORMATEXTENSIBLE*)rawdata; - if (FAILED(IDirectSoundBuffer_GetFormat((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)pActualFormat, requiredSize, NULL))) { + if (FAILED(mal_IDirectSoundBuffer_GetFormat((mal_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)pActualFormat, sizeof(rawdata), NULL))) { mal_device_uninit__dsound(pDevice); return mal_post_error(pDevice, "[DirectSound] Failed to retrieve the actual format of the playback device's primary buffer.", MAL_FORMAT_NOT_SUPPORTED); } + pDevice->internalFormat = mal_format_from_WAVEFORMATEX((WAVEFORMATEX*)pActualFormat); pDevice->internalChannels = pActualFormat->Format.nChannels; pDevice->internalSampleRate = pActualFormat->Format.nSamplesPerSec; - bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->format); // Get the internal channel map based on the channel mask. - mal_channel_mask_to_channel_map__win32(pActualFormat->dwChannelMask, pDevice->internalChannels, pDevice->internalChannelMap); + if (pActualFormat->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + mal_channel_mask_to_channel_map__win32(pActualFormat->dwChannelMask, pDevice->internalChannels, pDevice->internalChannelMap); + } else { + mal_channel_mask_to_channel_map__win32(wf.dwChannelMask, pDevice->internalChannels, pDevice->internalChannelMap); + } + + bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat); + // Meaning of dwFlags (from MSDN): @@ -4457,21 +6307,21 @@ static mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type // In the first version of DirectSound, the play cursor was significantly ahead of the actual playing sound on emulated // sound cards; it was directly behind the write cursor. Now, if the DSBCAPS_GETCURRENTPOSITION2 flag is specified, the // application can get a more accurate play cursor. - DSBUFFERDESC descDS; + MAL_DSBUFFERDESC descDS; mal_zero_object(&descDS); - descDS.dwSize = sizeof(DSBUFFERDESC); - descDS.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; + descDS.dwSize = sizeof(descDS); + descDS.dwFlags = MAL_DSBCAPS_CTRLPOSITIONNOTIFY | MAL_DSBCAPS_GLOBALFOCUS | MAL_DSBCAPS_GETCURRENTPOSITION2; descDS.dwBufferBytes = bufferSizeInBytes; descDS.lpwfxFormat = (WAVEFORMATEX*)&wf; - if (FAILED(IDirectSound_CreateSoundBuffer((LPDIRECTSOUND)pDevice->dsound.pPlayback, &descDS, (LPDIRECTSOUNDBUFFER*)&pDevice->dsound.pPlaybackBuffer, NULL))) { + if (FAILED(mal_IDirectSound_CreateSoundBuffer((mal_IDirectSound*)pDevice->dsound.pPlayback, &descDS, (mal_IDirectSoundBuffer**)&pDevice->dsound.pPlaybackBuffer, NULL))) { mal_device_uninit__dsound(pDevice); - return mal_post_error(pDevice, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's secondary buffer.", MAL_DSOUND_FAILED_TO_CREATE_BUFFER); + return mal_post_error(pDevice, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's secondary buffer.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } // Notifications are set up via a DIRECTSOUNDNOTIFY object which is retrieved from the buffer. - if (FAILED(IDirectSoundBuffer_QueryInterface((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, _MAL_GUID_IID_DirectSoundNotify, (void**)&pDevice->dsound.pNotify))) { + if (FAILED(mal_IDirectSoundBuffer_QueryInterface((mal_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, &MAL_GUID_IID_DirectSoundNotify, (void**)&pDevice->dsound.pNotify))) { mal_device_uninit__dsound(pDevice); - return mal_post_error(pDevice, "[DirectSound] IDirectSoundBuffer_QueryInterface() failed for playback device's IDirectSoundNotify object.", MAL_DSOUND_FAILED_TO_QUERY_INTERFACE); + return mal_post_error(pDevice, "[DirectSound] IDirectSoundBuffer_QueryInterface() failed for playback device's IDirectSoundNotify object.", MAL_API_NOT_FOUND); } } else { // The default buffer size is treated slightly differently for DirectSound which, for some reason, seems to @@ -4480,42 +6330,125 @@ static mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type pDevice->bufferSizeInFrames *= 2; // <-- Might need to fiddle with this to find a more ideal value. May even be able to just add a fixed amount rather than scaling. } - mal_DirectSoundCaptureCreateProc pDirectSoundCaptureCreate = (mal_DirectSoundCaptureCreateProc)GetProcAddress((HMODULE)pDevice->dsound.hDSoundDLL, "DirectSoundCaptureCreate"); - if (pDirectSoundCaptureCreate == NULL) { + if (FAILED(((mal_DirectSoundCaptureCreateProc)pContext->dsound.DirectSoundCaptureCreate)((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, (mal_IDirectSoundCapture**)&pDevice->dsound.pCapture, NULL))) { mal_device_uninit__dsound(pDevice); - return mal_post_error(pDevice, "[DirectSound] Could not find DirectSoundCreate().", MAL_API_NOT_FOUND); + return mal_post_error(pDevice, "[DirectSound] DirectSoundCaptureCreate() failed for capture device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } - if (FAILED(pDirectSoundCaptureCreate((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, (LPDIRECTSOUNDCAPTURE*)&pDevice->dsound.pCapture, NULL))) { + + MAL_DSCCAPS caps; + mal_zero_object(&caps); + caps.dwSize = sizeof(caps); + if (FAILED(mal_IDirectSoundCapture_GetCaps((mal_IDirectSoundCapture*)pDevice->dsound.pCapture, &caps))) { mal_device_uninit__dsound(pDevice); - return mal_post_error(pDevice, "[DirectSound] DirectSoundCaptureCreate() failed for capture device.", MAL_DSOUND_FAILED_TO_CREATE_DEVICE); + return mal_post_error(pDevice, "[DirectSound] IDirectSoundCapture_GetCaps() failed for capture device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } - bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format); + wf.Format.nChannels = (WORD)caps.dwChannels; - DSCBUFFERDESC descDS; + // The device can support multiple formats. We just go through the different formats in order of priority and + // pick the first one. This the same type of system as the WinMM backend. + wf.Format.wBitsPerSample = 16; + wf.Format.nSamplesPerSec = 48000; + + if (caps.dwChannels == 1) { + if ((caps.dwFormats & WAVE_FORMAT_48M16) != 0) { + wf.Format.nSamplesPerSec = 48000; + } else if ((caps.dwFormats & WAVE_FORMAT_44M16) != 0) { + wf.Format.nSamplesPerSec = 44100; + } else if ((caps.dwFormats & WAVE_FORMAT_2M16) != 0) { + wf.Format.nSamplesPerSec = 22050; + } else if ((caps.dwFormats & WAVE_FORMAT_1M16) != 0) { + wf.Format.nSamplesPerSec = 11025; + } else if ((caps.dwFormats & WAVE_FORMAT_96M16) != 0) { + wf.Format.nSamplesPerSec = 96000; + } else { + wf.Format.wBitsPerSample = 8; + if ((caps.dwFormats & WAVE_FORMAT_48M08) != 0) { + wf.Format.nSamplesPerSec = 48000; + } else if ((caps.dwFormats & WAVE_FORMAT_44M08) != 0) { + wf.Format.nSamplesPerSec = 44100; + } else if ((caps.dwFormats & WAVE_FORMAT_2M08) != 0) { + wf.Format.nSamplesPerSec = 22050; + } else if ((caps.dwFormats & WAVE_FORMAT_1M08) != 0) { + wf.Format.nSamplesPerSec = 11025; + } else if ((caps.dwFormats & WAVE_FORMAT_96M08) != 0) { + wf.Format.nSamplesPerSec = 96000; + } else { + wf.Format.wBitsPerSample = 16; // Didn't find it. Just fall back to 16-bit. + } + } + } else if (caps.dwChannels == 2) { + if ((caps.dwFormats & WAVE_FORMAT_48S16) != 0) { + wf.Format.nSamplesPerSec = 48000; + } else if ((caps.dwFormats & WAVE_FORMAT_44S16) != 0) { + wf.Format.nSamplesPerSec = 44100; + } else if ((caps.dwFormats & WAVE_FORMAT_2S16) != 0) { + wf.Format.nSamplesPerSec = 22050; + } else if ((caps.dwFormats & WAVE_FORMAT_1S16) != 0) { + wf.Format.nSamplesPerSec = 11025; + } else if ((caps.dwFormats & WAVE_FORMAT_96S16) != 0) { + wf.Format.nSamplesPerSec = 96000; + } else { + wf.Format.wBitsPerSample = 8; + if ((caps.dwFormats & WAVE_FORMAT_48S08) != 0) { + wf.Format.nSamplesPerSec = 48000; + } else if ((caps.dwFormats & WAVE_FORMAT_44S08) != 0) { + wf.Format.nSamplesPerSec = 44100; + } else if ((caps.dwFormats & WAVE_FORMAT_2S08) != 0) { + wf.Format.nSamplesPerSec = 22050; + } else if ((caps.dwFormats & WAVE_FORMAT_1S08) != 0) { + wf.Format.nSamplesPerSec = 11025; + } else if ((caps.dwFormats & WAVE_FORMAT_96S08) != 0) { + wf.Format.nSamplesPerSec = 96000; + } else { + wf.Format.wBitsPerSample = 16; // Didn't find it. Just fall back to 16-bit. + } + } + } + + wf.Format.nBlockAlign = (wf.Format.nChannels * wf.Format.wBitsPerSample) / 8; + wf.Format.nAvgBytesPerSec = wf.Format.nBlockAlign * wf.Format.nSamplesPerSec; + wf.Samples.wValidBitsPerSample = wf.Format.wBitsPerSample; + wf.SubFormat = MAL_GUID_KSDATAFORMAT_SUBTYPE_PCM; + + bufferSizeInBytes = pDevice->bufferSizeInFrames * wf.Format.nChannels * mal_get_bytes_per_sample(pDevice->format); + + MAL_DSCBUFFERDESC descDS; mal_zero_object(&descDS); descDS.dwSize = sizeof(descDS); descDS.dwFlags = 0; descDS.dwBufferBytes = bufferSizeInBytes; descDS.lpwfxFormat = (WAVEFORMATEX*)&wf; - LPDIRECTSOUNDCAPTUREBUFFER pDSCB_Temp; - if (FAILED(IDirectSoundCapture_CreateCaptureBuffer((LPDIRECTSOUNDCAPTURE)pDevice->dsound.pCapture, &descDS, &pDSCB_Temp, NULL))) { + if (FAILED(mal_IDirectSoundCapture_CreateCaptureBuffer((mal_IDirectSoundCapture*)pDevice->dsound.pCapture, &descDS, (mal_IDirectSoundCaptureBuffer**)&pDevice->dsound.pCaptureBuffer, NULL))) { mal_device_uninit__dsound(pDevice); - return mal_post_error(pDevice, "[DirectSound] IDirectSoundCapture_CreateCaptureBuffer() failed for capture device.", MAL_DSOUND_FAILED_TO_CREATE_BUFFER); + return mal_post_error(pDevice, "[DirectSound] IDirectSoundCapture_CreateCaptureBuffer() failed for capture device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } - HRESULT hr = IDirectSoundCapture_QueryInterface(pDSCB_Temp, _MAL_GUID_IID_IDirectSoundCaptureBuffer, (LPVOID*)&pDevice->dsound.pCaptureBuffer); - IDirectSoundCaptureBuffer_Release(pDSCB_Temp); - if (FAILED(hr)) { + // Get the _actual_ properties of the buffer. + char rawdata[1024]; + WAVEFORMATEXTENSIBLE* pActualFormat = (WAVEFORMATEXTENSIBLE*)rawdata; + if (FAILED(mal_IDirectSoundCaptureBuffer_GetFormat((mal_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, (WAVEFORMATEX*)pActualFormat, sizeof(rawdata), NULL))) { mal_device_uninit__dsound(pDevice); - return mal_post_error(pDevice, "[DirectSound] IDirectSoundCapture_QueryInterface() failed for capture device's IDirectSoundCaptureBuffer8 object.", MAL_DSOUND_FAILED_TO_QUERY_INTERFACE); + return mal_post_error(pDevice, "[DirectSound] Failed to retrieve the actual format of the capture device's buffer.", MAL_FORMAT_NOT_SUPPORTED); } + pDevice->internalFormat = mal_format_from_WAVEFORMATEX((WAVEFORMATEX*)pActualFormat); + pDevice->internalChannels = pActualFormat->Format.nChannels; + pDevice->internalSampleRate = pActualFormat->Format.nSamplesPerSec; + + // Get the internal channel map based on the channel mask. + if (pActualFormat->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + mal_channel_mask_to_channel_map__win32(pActualFormat->dwChannelMask, pDevice->internalChannels, pDevice->internalChannelMap); + } else { + mal_channel_mask_to_channel_map__win32(wf.dwChannelMask, pDevice->internalChannels, pDevice->internalChannelMap); + } + + // Notifications are set up via a DIRECTSOUNDNOTIFY object which is retrieved from the buffer. - if (FAILED(IDirectSoundCaptureBuffer_QueryInterface((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, _MAL_GUID_IID_DirectSoundNotify, (void**)&pDevice->dsound.pNotify))) { + if (FAILED(mal_IDirectSoundCaptureBuffer_QueryInterface((mal_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, &MAL_GUID_IID_DirectSoundNotify, (void**)&pDevice->dsound.pNotify))) { mal_device_uninit__dsound(pDevice); - return mal_post_error(pDevice, "[DirectSound] IDirectSoundCaptureBuffer_QueryInterface() failed for capture device's IDirectSoundNotify object.", MAL_DSOUND_FAILED_TO_QUERY_INTERFACE); + return mal_post_error(pDevice, "[DirectSound] IDirectSoundCaptureBuffer_QueryInterface() failed for capture device's IDirectSoundNotify object.", MAL_API_NOT_FOUND); } } @@ -4523,7 +6456,7 @@ static mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type // device is a playback or capture device. For a playback device we want to be notified when a period just starts playing, // whereas for a capture device we want to be notified when a period has just _finished_ capturing. mal_uint32 periodSizeInBytes = pDevice->bufferSizeInFrames / pDevice->periods; - DSBPOSITIONNOTIFY notifyPoints[MAL_MAX_PERIODS_DSOUND]; // One notification event for each period. + MAL_DSBPOSITIONNOTIFY notifyPoints[MAL_MAX_PERIODS_DSOUND]; // One notification event for each period. for (mal_uint32 i = 0; i < pDevice->periods; ++i) { pDevice->dsound.pNotifyEvents[i] = CreateEventA(NULL, FALSE, FALSE, NULL); if (pDevice->dsound.pNotifyEvents[i] == NULL) { @@ -4536,9 +6469,9 @@ static mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type notifyPoints[i].hEventNotify = pDevice->dsound.pNotifyEvents[i]; } - if (FAILED(IDirectSoundNotify_SetNotificationPositions((LPDIRECTSOUNDNOTIFY)pDevice->dsound.pNotify, pDevice->periods, notifyPoints))) { + if (FAILED(mal_IDirectSoundNotify_SetNotificationPositions((mal_IDirectSoundNotify*)pDevice->dsound.pNotify, pDevice->periods, notifyPoints))) { mal_device_uninit__dsound(pDevice); - return mal_post_error(pDevice, "[DirectSound] IDirectSoundNotify_SetNotificationPositions() failed.", MAL_DSOUND_FAILED_TO_SET_NOTIFICATIONS); + return mal_post_error(pDevice, "[DirectSound] IDirectSoundNotify_SetNotificationPositions() failed.", MAL_FAILED_TO_CREATE_EVENT); } // When the device is playing the worker thread will be waiting on a bunch of notification events. To return from @@ -4553,33 +6486,33 @@ static mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type } -static mal_result mal_device__start_backend__dsound(mal_device* pDevice) +mal_result mal_device__start_backend__dsound(mal_device* pDevice) { mal_assert(pDevice != NULL); if (pDevice->type == mal_device_type_playback) { // Before playing anything we need to grab an initial group of samples from the client. mal_uint32 framesToRead = pDevice->bufferSizeInFrames / pDevice->periods; - mal_uint32 desiredLockSize = framesToRead * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format); + mal_uint32 desiredLockSize = framesToRead * pDevice->channels * mal_get_bytes_per_sample(pDevice->format); void* pLockPtr; DWORD actualLockSize; void* pLockPtr2; DWORD actualLockSize2; - if (SUCCEEDED(IDirectSoundBuffer_Lock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, 0, desiredLockSize, &pLockPtr, &actualLockSize, &pLockPtr2, &actualLockSize2, 0))) { - framesToRead = actualLockSize / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels; + if (SUCCEEDED(mal_IDirectSoundBuffer_Lock((mal_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, desiredLockSize, &pLockPtr, &actualLockSize, &pLockPtr2, &actualLockSize2, 0))) { + framesToRead = actualLockSize / mal_get_bytes_per_sample(pDevice->format) / pDevice->channels; mal_device__read_frames_from_client(pDevice, framesToRead, pLockPtr); - IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, pLockPtr, actualLockSize, pLockPtr2, actualLockSize2); + mal_IDirectSoundBuffer_Unlock((mal_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, pLockPtr, actualLockSize, pLockPtr2, actualLockSize2); pDevice->dsound.lastProcessedFrame = framesToRead; - if (FAILED(IDirectSoundBuffer_Play((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, 0, 0, DSBPLAY_LOOPING))) { + if (FAILED(mal_IDirectSoundBuffer_Play((mal_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MAL_DSBPLAY_LOOPING))) { return mal_post_error(pDevice, "[DirectSound] IDirectSoundBuffer_Play() failed.", MAL_FAILED_TO_START_BACKEND_DEVICE); } } else { return mal_post_error(pDevice, "[DirectSound] IDirectSoundBuffer_Lock() failed.", MAL_FAILED_TO_MAP_DEVICE_BUFFER); } } else { - if (FAILED(IDirectSoundCaptureBuffer_Start((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, DSCBSTART_LOOPING))) { + if (FAILED(mal_IDirectSoundCaptureBuffer_Start((mal_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, MAL_DSCBSTART_LOOPING))) { return mal_post_error(pDevice, "[DirectSound] IDirectSoundCaptureBuffer_Start() failed.", MAL_FAILED_TO_START_BACKEND_DEVICE); } } @@ -4587,18 +6520,18 @@ static mal_result mal_device__start_backend__dsound(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__stop_backend__dsound(mal_device* pDevice) +mal_result mal_device__stop_backend__dsound(mal_device* pDevice) { mal_assert(pDevice != NULL); if (pDevice->type == mal_device_type_playback) { - if (FAILED(IDirectSoundBuffer_Stop((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer))) { + if (FAILED(mal_IDirectSoundBuffer_Stop((mal_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer))) { return mal_post_error(pDevice, "[DirectSound] IDirectSoundBuffer_Stop() failed.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); } - IDirectSoundBuffer_SetCurrentPosition((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, 0); + mal_IDirectSoundBuffer_SetCurrentPosition((mal_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0); } else { - if (FAILED(IDirectSoundCaptureBuffer_Stop((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer))) { + if (FAILED(mal_IDirectSoundCaptureBuffer_Stop((mal_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer))) { return mal_post_error(pDevice, "[DirectSound] IDirectSoundCaptureBuffer_Stop() failed.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); } } @@ -4606,7 +6539,7 @@ static mal_result mal_device__stop_backend__dsound(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__break_main_loop__dsound(mal_device* pDevice) +mal_result mal_device__break_main_loop__dsound(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -4617,7 +6550,7 @@ static mal_result mal_device__break_main_loop__dsound(mal_device* pDevice) return MAL_SUCCESS; } -static mal_bool32 mal_device__get_current_frame__dsound(mal_device* pDevice, mal_uint32* pCurrentPos) +mal_bool32 mal_device__get_current_frame__dsound(mal_device* pDevice, mal_uint32* pCurrentPos) { mal_assert(pDevice != NULL); mal_assert(pCurrentPos != NULL); @@ -4625,20 +6558,20 @@ static mal_bool32 mal_device__get_current_frame__dsound(mal_device* pDevice, mal DWORD dwCurrentPosition; if (pDevice->type == mal_device_type_playback) { - if (FAILED(IDirectSoundBuffer_GetCurrentPosition((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, NULL, &dwCurrentPosition))) { + if (FAILED(mal_IDirectSoundBuffer_GetCurrentPosition((mal_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, NULL, &dwCurrentPosition))) { return MAL_FALSE; } } else { - if (FAILED(IDirectSoundCaptureBuffer_GetCurrentPosition((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, &dwCurrentPosition, NULL))) { + if (FAILED(mal_IDirectSoundCaptureBuffer_GetCurrentPosition((mal_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, &dwCurrentPosition, NULL))) { return MAL_FALSE; } } - *pCurrentPos = (mal_uint32)dwCurrentPosition / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels; + *pCurrentPos = (mal_uint32)dwCurrentPosition / mal_get_bytes_per_sample(pDevice->format) / pDevice->channels; return MAL_TRUE; } -static mal_uint32 mal_device__get_available_frames__dsound(mal_device* pDevice) +mal_uint32 mal_device__get_available_frames__dsound(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -4680,7 +6613,7 @@ static mal_uint32 mal_device__get_available_frames__dsound(mal_device* pDevice) } } -static mal_uint32 mal_device__wait_for_frames__dsound(mal_device* pDevice) +mal_uint32 mal_device__wait_for_frames__dsound(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -4709,7 +6642,7 @@ static mal_uint32 mal_device__wait_for_frames__dsound(mal_device* pDevice) return mal_device__get_available_frames__dsound(pDevice); } -static mal_result mal_device__main_loop__dsound(mal_device* pDevice) +mal_result mal_device__main_loop__dsound(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -4728,37 +6661,37 @@ static mal_result mal_device__main_loop__dsound(mal_device* pDevice) return MAL_FALSE; } - DWORD lockOffset = pDevice->dsound.lastProcessedFrame * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format); - DWORD lockSize = framesAvailable * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format); + DWORD lockOffset = pDevice->dsound.lastProcessedFrame * pDevice->channels * mal_get_bytes_per_sample(pDevice->format); + DWORD lockSize = framesAvailable * pDevice->channels * mal_get_bytes_per_sample(pDevice->format); if (pDevice->type == mal_device_type_playback) { void* pLockPtr; DWORD actualLockSize; void* pLockPtr2; DWORD actualLockSize2; - if (FAILED(IDirectSoundBuffer_Lock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, lockOffset, lockSize, &pLockPtr, &actualLockSize, &pLockPtr2, &actualLockSize2, 0))) { + if (FAILED(mal_IDirectSoundBuffer_Lock((mal_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, lockOffset, lockSize, &pLockPtr, &actualLockSize, &pLockPtr2, &actualLockSize2, 0))) { return mal_post_error(pDevice, "[DirectSound] IDirectSoundBuffer_Lock() failed.", MAL_FAILED_TO_MAP_DEVICE_BUFFER); } - mal_uint32 frameCount = actualLockSize / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels; + mal_uint32 frameCount = actualLockSize / mal_get_bytes_per_sample(pDevice->format) / pDevice->channels; mal_device__read_frames_from_client(pDevice, frameCount, pLockPtr); pDevice->dsound.lastProcessedFrame = (pDevice->dsound.lastProcessedFrame + frameCount) % pDevice->bufferSizeInFrames; - IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, pLockPtr, actualLockSize, pLockPtr2, actualLockSize2); + mal_IDirectSoundBuffer_Unlock((mal_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, pLockPtr, actualLockSize, pLockPtr2, actualLockSize2); } else { void* pLockPtr; DWORD actualLockSize; void* pLockPtr2; DWORD actualLockSize2; - if (FAILED(IDirectSoundCaptureBuffer_Lock((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, lockOffset, lockSize, &pLockPtr, &actualLockSize, &pLockPtr2, &actualLockSize2, 0))) { + if (FAILED(mal_IDirectSoundCaptureBuffer_Lock((mal_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, lockOffset, lockSize, &pLockPtr, &actualLockSize, &pLockPtr2, &actualLockSize2, 0))) { return mal_post_error(pDevice, "[DirectSound] IDirectSoundCaptureBuffer_Lock() failed.", MAL_FAILED_TO_MAP_DEVICE_BUFFER); } - mal_uint32 frameCount = actualLockSize / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels; + mal_uint32 frameCount = actualLockSize / mal_get_bytes_per_sample(pDevice->format) / pDevice->channels; mal_device__send_frames_to_client(pDevice, frameCount, pLockPtr); pDevice->dsound.lastProcessedFrame = (pDevice->dsound.lastProcessedFrame + frameCount) % pDevice->bufferSizeInFrames; - IDirectSoundCaptureBuffer_Unlock((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, pLockPtr, actualLockSize, pLockPtr2, actualLockSize2); + mal_IDirectSoundCaptureBuffer_Unlock((mal_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, pLockPtr, actualLockSize, pLockPtr2, actualLockSize2); } } @@ -4774,26 +6707,37 @@ static mal_result mal_device__main_loop__dsound(mal_device* pDevice) // /////////////////////////////////////////////////////////////////////////////// #ifdef MAL_HAS_WINMM -#include -#if !defined(MAXULONG_PTR) -typedef size_t DWORD_PTR; -#endif - -#if !defined(WAVE_FORMAT_44M08) -#define WAVE_FORMAT_44M08 0x00000100 -#define WAVE_FORMAT_44S08 0x00000200 -#define WAVE_FORMAT_44M16 0x00000400 -#define WAVE_FORMAT_44S16 0x00000800 -#define WAVE_FORMAT_48M08 0x00001000 -#define WAVE_FORMAT_48S08 0x00002000 -#define WAVE_FORMAT_48M16 0x00004000 -#define WAVE_FORMAT_48S16 0x00008000 -#define WAVE_FORMAT_96M08 0x00010000 -#define WAVE_FORMAT_96S08 0x00020000 -#define WAVE_FORMAT_96M16 0x00040000 -#define WAVE_FORMAT_96S16 0x00080000 -#endif +// Some older compilers don't have WAVEOUTCAPS2A and WAVEINCAPS2A, so we'll need to write this ourselves. These structures +// are exactly the same as the older ones but they have a few GUIDs for manufacturer/product/name identification. I'm keeping +// the names the same as the Win32 library for consistency, but namespaced to avoid naming conflicts with the Win32 version. +typedef struct +{ + WORD wMid; + WORD wPid; + MMVERSION vDriverVersion; + CHAR szPname[MAXPNAMELEN]; + DWORD dwFormats; + WORD wChannels; + WORD wReserved1; + DWORD dwSupport; + GUID ManufacturerGuid; + GUID ProductGuid; + GUID NameGuid; +} MAL_WAVEOUTCAPS2A; +typedef struct +{ + WORD wMid; + WORD wPid; + MMVERSION vDriverVersion; + CHAR szPname[MAXPNAMELEN]; + DWORD dwFormats; + WORD wChannels; + WORD wReserved1; + GUID ManufacturerGuid; + GUID ProductGuid; + GUID NameGuid; +} MAL_WAVEINCAPS2A; typedef UINT (WINAPI * MAL_PFN_waveOutGetNumDevs)(void); typedef MMRESULT (WINAPI * MAL_PFN_waveOutGetDevCapsA)(UINT_PTR uDeviceID, LPWAVEOUTCAPSA pwoc, UINT cbwoc); @@ -4828,6 +6772,220 @@ mal_result mal_result_from_MMRESULT(MMRESULT resultMM) } } +char* mal_find_last_character(char* str, char ch) +{ + if (str == NULL) { + return NULL; + } + + char* last = NULL; + while (*str != '\0') { + if (*str == ch) { + last = str; + } + + str += 1; + } + + return last; +} + + +// Our own "WAVECAPS" structure that contains generic information shared between WAVEOUTCAPS2 and WAVEINCAPS2 so +// we can do things generically and typesafely. Names are being kept the same for consistency. +typedef struct +{ + CHAR szPname[MAXPNAMELEN]; + DWORD dwFormats; + WORD wChannels; + GUID NameGuid; +} MAL_WAVECAPSA; + +mal_result mal_context_get_device_info_from_WAVECAPS(mal_context* pContext, MAL_WAVECAPSA* pCaps, mal_device_info* pDeviceInfo) +{ + mal_assert(pContext != NULL); + mal_assert(pCaps != NULL); + mal_assert(pDeviceInfo != NULL); + + // Name / Description + // + // Unfortunately the name specified in WAVE(OUT/IN)CAPS2 is limited to 31 characters. This results in an unprofessional looking + // situation where the names of the devices are truncated. To help work around this, we need to look at the name GUID and try + // looking in the registry for the full name. If we can't find it there, we need to just fall back to the default name. + + // Set the default to begin with. + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), pCaps->szPname, (size_t)-1); + + // Now try the registry. There's a few things to consider here: + // - The name GUID can be null, in which we case we just need to stick to the original 31 characters. + // - If the name GUID is not present in the registry we'll also need to stick to the original 31 characters. + // - I like consistency, so I want the returned device names to be consistent with those returned by WASAPI and DirectSound. The + // problem, however is that WASAPI and DirectSound use " ()" format (such as "Speakers (High Definition Audio)"), + // but WinMM does not specificy the component name. From my admittedly limited testing, I've notice the component name seems to + // usually fit within the 31 characters of the fixed sized buffer, so what I'm going to do is parse that string for the component + // name, and then concatenate the name from the registry. + if (!mal_is_guid_equal(&pCaps->NameGuid, &MAL_GUID_NULL)) { + wchar_t guidStrW[256]; + if (((MAL_PFN_StringFromGUID2)pContext->win32.StringFromGUID2)(&pCaps->NameGuid, guidStrW, mal_countof(guidStrW)) > 0) { + char guidStr[256]; + WideCharToMultiByte(CP_UTF8, 0, guidStrW, -1, guidStr, sizeof(guidStr), 0, FALSE); + + char keyStr[1024]; + mal_strcpy_s(keyStr, sizeof(keyStr), "SYSTEM\\CurrentControlSet\\Control\\MediaCategories\\"); + mal_strcat_s(keyStr, sizeof(keyStr), guidStr); + + HKEY hKey; + LONG result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyStr, 0, KEY_READ, &hKey); + if (result == ERROR_SUCCESS) { + BYTE nameFromReg[512]; + DWORD nameFromRegSize = sizeof(nameFromReg); + result = RegQueryValueExA(hKey, "Name", 0, NULL, (LPBYTE)nameFromReg, (LPDWORD)&nameFromRegSize); + RegCloseKey(hKey); + + if (result == ERROR_SUCCESS) { + // We have the value from the registry, so now we need to construct the name string. + char name[1024]; + if (mal_strcpy_s(name, sizeof(name), pDeviceInfo->name) == 0) { + char* nameBeg = mal_find_last_character(name, '('); + if (nameBeg != NULL) { + size_t leadingLen = (nameBeg - name); + mal_strncpy_s(nameBeg + 1, sizeof(name) - leadingLen, (const char*)nameFromReg, (size_t)-1); + + // The closing ")", if it can fit. + if (leadingLen + nameFromRegSize < sizeof(name)-1) { + mal_strcat_s(name, sizeof(name), ")"); + } + + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), name, (size_t)-1); + } + } + } + } + } + } + + return MAL_SUCCESS; +} + +mal_result mal_context_get_device_info_from_WAVEOUTCAPS2(mal_context* pContext, MAL_WAVEOUTCAPS2A* pCaps, mal_device_info* pDeviceInfo) +{ + mal_assert(pContext != NULL); + mal_assert(pCaps != NULL); + mal_assert(pDeviceInfo != NULL); + + MAL_WAVECAPSA caps; + mal_copy_memory(caps.szPname, pCaps->szPname, sizeof(caps.szPname)); + caps.dwFormats = pCaps->dwFormats; + caps.wChannels = pCaps->wChannels; + caps.NameGuid = pCaps->NameGuid; + return mal_context_get_device_info_from_WAVECAPS(pContext, &caps, pDeviceInfo); +} + +mal_result mal_context_get_device_info_from_WAVEINCAPS2(mal_context* pContext, MAL_WAVEINCAPS2A* pCaps, mal_device_info* pDeviceInfo) +{ + mal_assert(pContext != NULL); + mal_assert(pCaps != NULL); + mal_assert(pDeviceInfo != NULL); + + MAL_WAVECAPSA caps; + mal_copy_memory(caps.szPname, pCaps->szPname, sizeof(caps.szPname)); + caps.dwFormats = pCaps->dwFormats; + caps.wChannels = pCaps->wChannels; + caps.NameGuid = pCaps->NameGuid; + return mal_context_get_device_info_from_WAVECAPS(pContext, &caps, pDeviceInfo); +} + + +mal_bool32 mal_context_is_device_id_equal__winmm(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) +{ + mal_assert(pContext != NULL); + mal_assert(pID0 != NULL); + mal_assert(pID1 != NULL); + (void)pContext; + + return pID0->winmm == pID1->winmm; +} + +mal_result mal_context_enumerate_devices__winmm(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +{ + mal_assert(pContext != NULL); + mal_assert(callback != NULL); + + // Playback. + UINT playbackDeviceCount = ((MAL_PFN_waveOutGetNumDevs)pContext->winmm.waveOutGetNumDevs)(); + for (UINT iPlaybackDevice = 0; iPlaybackDevice < playbackDeviceCount; ++iPlaybackDevice) { + MAL_WAVEOUTCAPS2A caps; + mal_zero_object(&caps); + MMRESULT result = ((MAL_PFN_waveOutGetDevCapsA)pContext->winmm.waveOutGetDevCapsA)(iPlaybackDevice, (WAVEOUTCAPSA*)&caps, sizeof(caps)); + if (result == MMSYSERR_NOERROR) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + deviceInfo.id.winmm = iPlaybackDevice; + + if (mal_context_get_device_info_from_WAVEOUTCAPS2(pContext, &caps, &deviceInfo) == MAL_SUCCESS) { + mal_bool32 cbResult = callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); + if (cbResult == MAL_FALSE) { + return MAL_SUCCESS; // Enumeration was stopped. + } + } + } + } + + // Capture. + UINT captureDeviceCount = ((MAL_PFN_waveInGetNumDevs)pContext->winmm.waveInGetNumDevs)(); + for (UINT iCaptureDevice = 0; iCaptureDevice < captureDeviceCount; ++iCaptureDevice) { + MAL_WAVEINCAPS2A caps; + mal_zero_object(&caps); + MMRESULT result = ((MAL_PFN_waveInGetDevCapsA)pContext->winmm.waveInGetDevCapsA)(iCaptureDevice, (WAVEINCAPSA*)&caps, sizeof(caps)); + if (result == MMSYSERR_NOERROR) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + deviceInfo.id.winmm = iCaptureDevice; + + if (mal_context_get_device_info_from_WAVEINCAPS2(pContext, &caps, &deviceInfo) == MAL_SUCCESS) { + mal_bool32 cbResult = callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); + if (cbResult == MAL_FALSE) { + return MAL_SUCCESS; // Enumeration was stopped. + } + } + } + } + + return MAL_SUCCESS; +} + +mal_result mal_context_get_device_info__winmm(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +{ + mal_assert(pContext != NULL); + (void)shareMode; + + UINT winMMDeviceID = 0; + if (pDeviceID != NULL) { + winMMDeviceID = (UINT)pDeviceID->winmm; + } + + pDeviceInfo->id.winmm = winMMDeviceID; + + if (deviceType == mal_device_type_playback) { + MAL_WAVEOUTCAPS2A caps; + mal_zero_object(&caps); + MMRESULT result = ((MAL_PFN_waveOutGetDevCapsA)pContext->winmm.waveOutGetDevCapsA)(winMMDeviceID, (WAVEOUTCAPSA*)&caps, sizeof(caps)); + if (result == MMSYSERR_NOERROR) { + return mal_context_get_device_info_from_WAVEOUTCAPS2(pContext, &caps, pDeviceInfo); + } + } else { + MAL_WAVEINCAPS2A caps; + mal_zero_object(&caps); + MMRESULT result = ((MAL_PFN_waveInGetDevCapsA)pContext->winmm.waveInGetDevCapsA)(winMMDeviceID, (WAVEINCAPSA*)&caps, sizeof(caps)); + if (result == MMSYSERR_NOERROR) { + return mal_context_get_device_info_from_WAVEINCAPS2(pContext, &caps, pDeviceInfo); + } + } + + return MAL_NO_DEVICE; +} + + mal_result mal_context_init__winmm(mal_context* pContext) { mal_assert(pContext != NULL); @@ -4855,6 +7013,10 @@ mal_result mal_context_init__winmm(mal_context* pContext) pContext->winmm.waveInStart = mal_dlsym(pContext->winmm.hWinMM, "waveInStart"); pContext->winmm.waveInReset = mal_dlsym(pContext->winmm.hWinMM, "waveInReset"); + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__winmm; + pContext->onEnumDevices = mal_context_enumerate_devices__winmm; + pContext->onGetDeviceInfo = mal_context_get_device_info__winmm; + return MAL_SUCCESS; } @@ -4867,59 +7029,7 @@ mal_result mal_context_uninit__winmm(mal_context* pContext) return MAL_SUCCESS; } -static mal_result mal_enumerate_devices__winmm(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) -{ - (void)pContext; - - mal_uint32 infoSize = *pCount; - *pCount = 0; - - if (type == mal_device_type_playback) { - UINT deviceCount = ((MAL_PFN_waveOutGetNumDevs)pContext->winmm.waveOutGetNumDevs)(); - for (UINT iDevice = 0; iDevice < deviceCount; ++iDevice) { - if (pInfo != NULL) { - if (infoSize > 0) { - WAVEOUTCAPSA caps; - MMRESULT result = ((MAL_PFN_waveOutGetDevCapsA)pContext->winmm.waveOutGetDevCapsA)(iDevice, &caps, sizeof(caps)); - if (result == MMSYSERR_NOERROR) { - pInfo->id.winmm = iDevice; - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), caps.szPname, (size_t)-1); - } - - pInfo += 1; - infoSize -= 1; - *pCount += 1; - } - } else { - *pCount += 1; - } - } - } else { - UINT deviceCount = ((MAL_PFN_waveInGetNumDevs)pContext->winmm.waveInGetNumDevs)(); - for (UINT iDevice = 0; iDevice < deviceCount; ++iDevice) { - if (pInfo != NULL) { - if (infoSize > 0) { - WAVEINCAPSA caps; - MMRESULT result = ((MAL_PFN_waveInGetDevCapsA)pContext->winmm.waveInGetDevCapsA)(iDevice, &caps, sizeof(caps)); - if (result == MMSYSERR_NOERROR) { - pInfo->id.winmm = iDevice; - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), caps.szPname, (size_t)-1); - } - - pInfo += 1; - infoSize -= 1; - *pCount += 1; - } - } else { - *pCount += 1; - } - } - } - - return MAL_SUCCESS; -} - -static void mal_device_uninit__winmm(mal_device* pDevice) +void mal_device_uninit__winmm(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -4933,16 +7043,11 @@ static void mal_device_uninit__winmm(mal_device* pDevice) CloseHandle((HANDLE)pDevice->winmm.hEvent); } -static mal_result mal_device_init__winmm(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +mal_result mal_device_init__winmm(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) { (void)pContext; mal_uint32 heapSize; - mal_uint32 iBit; - - WORD closestBitsPerSample = 0; - WORD closestChannels = 0; - DWORD closestSampleRate = 0; mal_assert(pDevice != NULL); mal_zero_object(&pDevice->winmm); @@ -4965,7 +7070,7 @@ static mal_result mal_device_init__winmm(mal_context* pContext, mal_device_type wf.wFormatTag = WAVE_FORMAT_PCM; wf.nChannels = (WORD)pConfig->channels; wf.nSamplesPerSec = (DWORD)pConfig->sampleRate; - wf.wBitsPerSample = (WORD)mal_get_sample_size_in_bytes(pConfig->format)*8; + wf.wBitsPerSample = (WORD)mal_get_bytes_per_sample(pConfig->format)*8; if (wf.nChannels > 2) { wf.nChannels = 2; @@ -4994,210 +7099,94 @@ static mal_result mal_device_init__winmm(mal_context* pContext, mal_device_type // Change the format based on the closest match of the supported standard formats. DWORD dwFormats = 0; + WORD wChannels = 0; if (type == mal_device_type_playback) { WAVEOUTCAPSA caps; if (((MAL_PFN_waveOutGetDevCapsA)pContext->winmm.waveOutGetDevCapsA)(winMMDeviceID, &caps, sizeof(caps)) == MMSYSERR_NOERROR) { dwFormats = caps.dwFormats; + wChannels = caps.wChannels; } else { - errorMsg = "[WinMM] Failed to retrieve internal device caps.", errorCode = MAL_WINMM_FAILED_TO_GET_DEVICE_CAPS; + errorMsg = "[WinMM] Failed to retrieve internal device caps.", errorCode = MAL_FORMAT_NOT_SUPPORTED; goto on_error; } } else { WAVEINCAPSA caps; if (((MAL_PFN_waveInGetDevCapsA)pContext->winmm.waveInGetDevCapsA)(winMMDeviceID, &caps, sizeof(caps)) == MMSYSERR_NOERROR) { dwFormats = caps.dwFormats; + wChannels = caps.wChannels; } else { - errorMsg = "[WinMM] Failed to retrieve internal device caps.", errorCode = MAL_WINMM_FAILED_TO_GET_DEVICE_CAPS; + errorMsg = "[WinMM] Failed to retrieve internal device caps.", errorCode = MAL_FORMAT_NOT_SUPPORTED; goto on_error; } } if (dwFormats == 0) { - errorMsg = "[WinMM] Failed to retrieve the supported formats for the internal device.", errorCode = MAL_WINMM_FAILED_TO_GET_SUPPORTED_FORMATS; + errorMsg = "[WinMM] Failed to retrieve the supported formats for the internal device.", errorCode = MAL_FORMAT_NOT_SUPPORTED; goto on_error; } - for (iBit = 0; iBit < 32; ++iBit) { - WORD formatBitsPerSample = 0; - WORD formatChannels = 0; - DWORD formatSampleRate = 0; - - DWORD format = (dwFormats & (1 << iBit)); - if (format != 0) { - switch (format) - { - case WAVE_FORMAT_1M08: - { - formatBitsPerSample = 8; - formatChannels = 1; - formatSampleRate = 110025; - } break; - case WAVE_FORMAT_1M16: - { - formatBitsPerSample = 16; - formatChannels = 1; - formatSampleRate = 110025; - } break; - case WAVE_FORMAT_1S08: - { - formatBitsPerSample = 8; - formatChannels = 2; - formatSampleRate = 110025; - } break; - case WAVE_FORMAT_1S16: - { - formatBitsPerSample = 16; - formatChannels = 2; - formatSampleRate = 110025; - } break; - case WAVE_FORMAT_2M08: - { - formatBitsPerSample = 8; - formatChannels = 1; - formatSampleRate = 22050; - } break; - case WAVE_FORMAT_2M16: - { - formatBitsPerSample = 16; - formatChannels = 1; - formatSampleRate = 22050; - } break; - case WAVE_FORMAT_2S08: - { - formatBitsPerSample = 8; - formatChannels = 2; - formatSampleRate = 22050; - } break; - case WAVE_FORMAT_2S16: - { - formatBitsPerSample = 16; - formatChannels = 2; - formatSampleRate = 22050; - } break; - case WAVE_FORMAT_44M08: - { - formatBitsPerSample = 8; - formatChannels = 1; - formatSampleRate = 44100; - } break; - case WAVE_FORMAT_44M16: - { - formatBitsPerSample = 16; - formatChannels = 1; - formatSampleRate = 44100; - } break; - case WAVE_FORMAT_44S08: - { - formatBitsPerSample = 8; - formatChannels = 2; - formatSampleRate = 44100; - } break; - case WAVE_FORMAT_44S16: - { - formatBitsPerSample = 16; - formatChannels = 2; - formatSampleRate = 44100; - } break; - case WAVE_FORMAT_48M08: - { - formatBitsPerSample = 8; - formatChannels = 1; - formatSampleRate = 48000; - } break; - case WAVE_FORMAT_48M16: - { - formatBitsPerSample = 16; - formatChannels = 1; - formatSampleRate = 48000; - } break; - case WAVE_FORMAT_48S08: - { - formatBitsPerSample = 8; - formatChannels = 2; - formatSampleRate = 48000; - } break; - case WAVE_FORMAT_48S16: - { - formatBitsPerSample = 16; - formatChannels = 2; - formatSampleRate = 48000; - } break; - case WAVE_FORMAT_96M08: - { - formatBitsPerSample = 8; - formatChannels = 1; - formatSampleRate = 96000; - } break; - case WAVE_FORMAT_96M16: - { - formatBitsPerSample = 16; - formatChannels = 1; - formatSampleRate = 96000; - } break; - case WAVE_FORMAT_96S08: - { - formatBitsPerSample = 8; - formatChannels = 2; - formatSampleRate = 96000; - } break; - case WAVE_FORMAT_96S16: - { - formatBitsPerSample = 16; - formatChannels = 2; - formatSampleRate = 96000; - } break; - default: - { - errorMsg = "[WinMM] The internal device does not support any of the standard formats.", errorCode = MAL_ERROR; // <-- Should never hit this. - goto on_error; - } break; - } - - if (formatBitsPerSample == wf.wBitsPerSample && formatChannels == wf.nChannels && formatSampleRate == wf.nSamplesPerSec) { - break; // It's an exact match. + if (wChannels == 1) { + wf.nChannels = 1; + wf.wBitsPerSample = 16; + if ((dwFormats & WAVE_FORMAT_48M16) != 0) { + wf.nSamplesPerSec = 48000; + } else if ((dwFormats & WAVE_FORMAT_44M16) != 0) { + wf.nSamplesPerSec = 44100; + } else if ((dwFormats & WAVE_FORMAT_2M16) != 0) { + wf.nSamplesPerSec = 22050; + } else if ((dwFormats & WAVE_FORMAT_1M16) != 0) { + wf.nSamplesPerSec = 11025; + } else if ((dwFormats & WAVE_FORMAT_96M16) != 0) { + wf.nSamplesPerSec = 96000; + } else { + wf.wBitsPerSample = 8; + if ((dwFormats & WAVE_FORMAT_48M08) != 0) { + wf.nSamplesPerSec = 48000; + } else if ((dwFormats & WAVE_FORMAT_44M08) != 0) { + wf.nSamplesPerSec = 44100; + } else if ((dwFormats & WAVE_FORMAT_2M08) != 0) { + wf.nSamplesPerSec = 22050; + } else if ((dwFormats & WAVE_FORMAT_1M08) != 0) { + wf.nSamplesPerSec = 11025; + } else if ((dwFormats & WAVE_FORMAT_96M08) != 0) { + wf.nSamplesPerSec = 96000; } else { - // It's not an exact match. Compare it with the closest match. - if (closestBitsPerSample == 0) { - // This is the first format, so nothing to compare against. - closestBitsPerSample = formatBitsPerSample; - closestChannels = formatChannels; - closestSampleRate = formatSampleRate; - } else { - // Prefer the channel count be the same over the others. - if (formatChannels != closestChannels) { - // Channels aren't equal. Favour the one equal to our desired channel count. - if (formatChannels == wf.nChannels) { - closestBitsPerSample = formatBitsPerSample; - closestChannels = formatChannels; - closestSampleRate = formatSampleRate; - } - } else { - // The channels are equal. Look at the format now. - if (formatBitsPerSample != closestBitsPerSample) { - if (formatBitsPerSample == wf.wBitsPerSample) { - closestBitsPerSample = formatBitsPerSample; - closestChannels = formatChannels; - closestSampleRate = formatSampleRate; - } - } else { - // Both the channels and formats are the same, so now just favour whichever's sample rate is closest to the requested rate. - mal_uint32 closestRateDiff = (closestSampleRate > wf.nSamplesPerSec) ? (closestSampleRate - wf.nSamplesPerSec) : (wf.nSamplesPerSec - closestSampleRate); - mal_uint32 formatRateDiff = (formatSampleRate > wf.nSamplesPerSec) ? (formatSampleRate - wf.nSamplesPerSec) : (wf.nSamplesPerSec - formatSampleRate); - if (formatRateDiff < closestRateDiff) { - closestBitsPerSample = formatBitsPerSample; - closestChannels = formatChannels; - closestSampleRate = formatSampleRate; - } - } - } - } + errorMsg = "[WinMM] Could not find appropriate format for internal device.", errorCode = MAL_FORMAT_NOT_SUPPORTED; + goto on_error; + } + } + } else { + wf.nChannels = 2; + wf.wBitsPerSample = 16; + if ((dwFormats & WAVE_FORMAT_48S16) != 0) { + wf.nSamplesPerSec = 48000; + } else if ((dwFormats & WAVE_FORMAT_44S16) != 0) { + wf.nSamplesPerSec = 44100; + } else if ((dwFormats & WAVE_FORMAT_2S16) != 0) { + wf.nSamplesPerSec = 22050; + } else if ((dwFormats & WAVE_FORMAT_1S16) != 0) { + wf.nSamplesPerSec = 11025; + } else if ((dwFormats & WAVE_FORMAT_96S16) != 0) { + wf.nSamplesPerSec = 96000; + } else { + wf.wBitsPerSample = 8; + if ((dwFormats & WAVE_FORMAT_48S08) != 0) { + wf.nSamplesPerSec = 48000; + } else if ((dwFormats & WAVE_FORMAT_44S08) != 0) { + wf.nSamplesPerSec = 44100; + } else if ((dwFormats & WAVE_FORMAT_2S08) != 0) { + wf.nSamplesPerSec = 22050; + } else if ((dwFormats & WAVE_FORMAT_1S08) != 0) { + wf.nSamplesPerSec = 11025; + } else if ((dwFormats & WAVE_FORMAT_96S08) != 0) { + wf.nSamplesPerSec = 96000; + } else { + errorMsg = "[WinMM] Could not find appropriate format for internal device.", errorCode = MAL_FORMAT_NOT_SUPPORTED; + goto on_error; } } } - wf.wBitsPerSample = closestBitsPerSample; - wf.nChannels = closestChannels; - wf.nSamplesPerSec = closestSampleRate; wf.nBlockAlign = (wf.nChannels * wf.wBitsPerSample) / 8; wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec; @@ -5244,7 +7233,7 @@ static mal_result mal_device_init__winmm(mal_context* pContext, mal_device_type // Just use the default channel mapping. WinMM only supports mono or stereo anyway so it'll reliably be left/right order for stereo. - mal_get_default_channel_mapping(pDevice->pContext->backend, pDevice->internalChannels, pDevice->internalChannelMap); + mal_get_standard_channel_map(mal_standard_channel_map_microsoft, pDevice->internalChannels, pDevice->internalChannelMap); // Latency with WinMM seems pretty bad from my testing... Need to increase the default buffer size. @@ -5258,7 +7247,7 @@ static mal_result mal_device_init__winmm(mal_context* pContext, mal_device_type // The size of the intermediary buffer needs to be able to fit every fragment. pDevice->winmm.fragmentSizeInFrames = pDevice->bufferSizeInFrames / pDevice->periods; - pDevice->winmm.fragmentSizeInBytes = pDevice->winmm.fragmentSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat); + pDevice->winmm.fragmentSizeInBytes = pDevice->winmm.fragmentSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat); heapSize = (sizeof(WAVEHDR) * pDevice->periods) + (pDevice->winmm.fragmentSizeInBytes * pDevice->periods); pDevice->winmm._pHeapData = (mal_uint8*)mal_malloc(heapSize); @@ -5287,7 +7276,7 @@ on_error: } -static mal_result mal_device__start_backend__winmm(mal_device* pDevice) +mal_result mal_device__start_backend__winmm(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -5351,7 +7340,7 @@ static mal_result mal_device__start_backend__winmm(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__stop_backend__winmm(mal_device* pDevice) +mal_result mal_device__stop_backend__winmm(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -5386,7 +7375,7 @@ static mal_result mal_device__stop_backend__winmm(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__break_main_loop__winmm(mal_device* pDevice) +mal_result mal_device__break_main_loop__winmm(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -5396,7 +7385,7 @@ static mal_result mal_device__break_main_loop__winmm(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__main_loop__winmm(mal_device* pDevice) +mal_result mal_device__main_loop__winmm(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -5447,7 +7436,7 @@ static mal_result mal_device__main_loop__winmm(mal_device* pDevice) } } else { // Capture. - mal_uint32 framesCaptured = (mal_uint32)(((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwBytesRecorded) / pDevice->internalChannels / mal_get_sample_size_in_bytes(pDevice->internalFormat); + mal_uint32 framesCaptured = (mal_uint32)(((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwBytesRecorded) / pDevice->internalChannels / mal_get_bytes_per_sample(pDevice->internalFormat); if (framesCaptured > 0) { mal_device__send_frames_to_client(pDevice, framesCaptured, ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].lpData); } @@ -5508,17 +7497,279 @@ static mal_result mal_device__main_loop__winmm(mal_device* pDevice) + /////////////////////////////////////////////////////////////////////////////// // // ALSA Backend // /////////////////////////////////////////////////////////////////////////////// #ifdef MAL_HAS_ALSA + +#ifdef MAL_NO_RUNTIME_LINKING #include +typedef snd_pcm_uframes_t mal_snd_pcm_uframes_t; +typedef snd_pcm_sframes_t mal_snd_pcm_sframes_t; +typedef snd_pcm_stream_t mal_snd_pcm_stream_t; +typedef snd_pcm_format_t mal_snd_pcm_format_t; +typedef snd_pcm_access_t mal_snd_pcm_access_t; +typedef snd_pcm_t mal_snd_pcm_t; +typedef snd_pcm_hw_params_t mal_snd_pcm_hw_params_t; +typedef snd_pcm_sw_params_t mal_snd_pcm_sw_params_t; +typedef snd_pcm_format_mask_t mal_snd_pcm_format_mask_t; +typedef snd_pcm_info_t mal_snd_pcm_info_t; +typedef snd_pcm_channel_area_t mal_snd_pcm_channel_area_t; +typedef snd_pcm_chmap_t mal_snd_pcm_chmap_t; + +// snd_pcm_stream_t +#define MAL_SND_PCM_STREAM_PLAYBACK SND_PCM_STREAM_PLAYBACK +#define MAL_SND_PCM_STREAM_CAPTURE SND_PCM_STREAM_CAPTURE + +// snd_pcm_format_t +#define MAL_SND_PCM_FORMAT_UNKNOWN SND_PCM_FORMAT_UNKNOWN +#define MAL_SND_PCM_FORMAT_U8 SND_PCM_FORMAT_U8 +#define MAL_SND_PCM_FORMAT_S16_LE SND_PCM_FORMAT_S16_LE +#define MAL_SND_PCM_FORMAT_S16_BE SND_PCM_FORMAT_S16_BE +#define MAL_SND_PCM_FORMAT_S24_LE SND_PCM_FORMAT_S24_LE +#define MAL_SND_PCM_FORMAT_S24_BE SND_PCM_FORMAT_S24_BE +#define MAL_SND_PCM_FORMAT_S32_LE SND_PCM_FORMAT_S32_LE +#define MAL_SND_PCM_FORMAT_S32_BE SND_PCM_FORMAT_S32_BE +#define MAL_SND_PCM_FORMAT_FLOAT_LE SND_PCM_FORMAT_FLOAT_LE +#define MAL_SND_PCM_FORMAT_FLOAT_BE SND_PCM_FORMAT_FLOAT_BE +#define MAL_SND_PCM_FORMAT_FLOAT64_LE SND_PCM_FORMAT_FLOAT64_LE +#define MAL_SND_PCM_FORMAT_FLOAT64_BE SND_PCM_FORMAT_FLOAT64_BE +#define MAL_SND_PCM_FORMAT_MU_LAW SND_PCM_FORMAT_MU_LAW +#define MAL_SND_PCM_FORMAT_A_LAW SND_PCM_FORMAT_A_LAW +#define MAL_SND_PCM_FORMAT_S24_3LE SND_PCM_FORMAT_S24_3LE +#define MAL_SND_PCM_FORMAT_S24_3BE SND_PCM_FORMAT_S24_3BE + +// mal_snd_pcm_access_t +#define MAL_SND_PCM_ACCESS_MMAP_INTERLEAVED SND_PCM_ACCESS_MMAP_INTERLEAVED +#define MAL_SND_PCM_ACCESS_MMAP_NONINTERLEAVED SND_PCM_ACCESS_MMAP_NONINTERLEAVED +#define MAL_SND_PCM_ACCESS_MMAP_COMPLEX SND_PCM_ACCESS_MMAP_COMPLEX +#define MAL_SND_PCM_ACCESS_RW_INTERLEAVED SND_PCM_ACCESS_RW_INTERLEAVED +#define MAL_SND_PCM_ACCESS_RW_NONINTERLEAVED SND_PCM_ACCESS_RW_NONINTERLEAVED + +// Channel positions. +#define MAL_SND_CHMAP_UNKNOWN SND_CHMAP_UNKNOWN +#define MAL_SND_CHMAP_NA SND_CHMAP_NA +#define MAL_SND_CHMAP_MONO SND_CHMAP_MONO +#define MAL_SND_CHMAP_FL SND_CHMAP_FL +#define MAL_SND_CHMAP_FR SND_CHMAP_FR +#define MAL_SND_CHMAP_RL SND_CHMAP_RL +#define MAL_SND_CHMAP_RR SND_CHMAP_RR +#define MAL_SND_CHMAP_FC SND_CHMAP_FC +#define MAL_SND_CHMAP_LFE SND_CHMAP_LFE +#define MAL_SND_CHMAP_SL SND_CHMAP_SL +#define MAL_SND_CHMAP_SR SND_CHMAP_SR +#define MAL_SND_CHMAP_RC SND_CHMAP_RC +#define MAL_SND_CHMAP_FLC SND_CHMAP_FLC +#define MAL_SND_CHMAP_FRC SND_CHMAP_FRC +#define MAL_SND_CHMAP_RLC SND_CHMAP_RLC +#define MAL_SND_CHMAP_RRC SND_CHMAP_RRC +#define MAL_SND_CHMAP_FLW SND_CHMAP_FLW +#define MAL_SND_CHMAP_FRW SND_CHMAP_FRW +#define MAL_SND_CHMAP_FLH SND_CHMAP_FLH +#define MAL_SND_CHMAP_FCH SND_CHMAP_FCH +#define MAL_SND_CHMAP_FRH SND_CHMAP_FRH +#define MAL_SND_CHMAP_TC SND_CHMAP_TC +#define MAL_SND_CHMAP_TFL SND_CHMAP_TFL +#define MAL_SND_CHMAP_TFR SND_CHMAP_TFR +#define MAL_SND_CHMAP_TFC SND_CHMAP_TFC +#define MAL_SND_CHMAP_TRL SND_CHMAP_TRL +#define MAL_SND_CHMAP_TRR SND_CHMAP_TRR +#define MAL_SND_CHMAP_TRC SND_CHMAP_TRC +#define MAL_SND_CHMAP_TFLC SND_CHMAP_TFLC +#define MAL_SND_CHMAP_TFRC SND_CHMAP_TFRC +#define MAL_SND_CHMAP_TSL SND_CHMAP_TSL +#define MAL_SND_CHMAP_TSR SND_CHMAP_TSR +#define MAL_SND_CHMAP_LLFE SND_CHMAP_LLFE +#define MAL_SND_CHMAP_RLFE SND_CHMAP_RLFE +#define MAL_SND_CHMAP_BC SND_CHMAP_BC +#define MAL_SND_CHMAP_BLC SND_CHMAP_BLC +#define MAL_SND_CHMAP_BRC SND_CHMAP_BRC + +// Open mode flags. +#define MAL_SND_PCM_NO_AUTO_RESAMPLE SND_PCM_NO_AUTO_RESAMPLE +#define MAL_SND_PCM_NO_AUTO_CHANNELS SND_PCM_NO_AUTO_CHANNELS +#define MAL_SND_PCM_NO_AUTO_FORMAT SND_PCM_NO_AUTO_FORMAT +#else +#include // For EPIPE, etc. +typedef unsigned long mal_snd_pcm_uframes_t; +typedef long mal_snd_pcm_sframes_t; +typedef int mal_snd_pcm_stream_t; +typedef int mal_snd_pcm_format_t; +typedef int mal_snd_pcm_access_t; +typedef struct mal_snd_pcm_t mal_snd_pcm_t; +typedef struct mal_snd_pcm_hw_params_t mal_snd_pcm_hw_params_t; +typedef struct mal_snd_pcm_sw_params_t mal_snd_pcm_sw_params_t; +typedef struct mal_snd_pcm_format_mask_t mal_snd_pcm_format_mask_t; +typedef struct mal_snd_pcm_info_t mal_snd_pcm_info_t; +typedef struct +{ + void* addr; + unsigned int first; + unsigned int step; +} mal_snd_pcm_channel_area_t; +typedef struct +{ + unsigned int channels; + unsigned int pos[0]; +} mal_snd_pcm_chmap_t; + +// snd_pcm_stream_t +#define MAL_SND_PCM_STREAM_PLAYBACK 0 +#define MAL_SND_PCM_STREAM_CAPTURE 1 + +// snd_pcm_format_t +#define MAL_SND_PCM_FORMAT_UNKNOWN -1 +#define MAL_SND_PCM_FORMAT_U8 1 +#define MAL_SND_PCM_FORMAT_S16_LE 2 +#define MAL_SND_PCM_FORMAT_S16_BE 3 +#define MAL_SND_PCM_FORMAT_S24_LE 6 +#define MAL_SND_PCM_FORMAT_S24_BE 7 +#define MAL_SND_PCM_FORMAT_S32_LE 10 +#define MAL_SND_PCM_FORMAT_S32_BE 11 +#define MAL_SND_PCM_FORMAT_FLOAT_LE 14 +#define MAL_SND_PCM_FORMAT_FLOAT_BE 15 +#define MAL_SND_PCM_FORMAT_FLOAT64_LE 16 +#define MAL_SND_PCM_FORMAT_FLOAT64_BE 17 +#define MAL_SND_PCM_FORMAT_MU_LAW 20 +#define MAL_SND_PCM_FORMAT_A_LAW 21 +#define MAL_SND_PCM_FORMAT_S24_3LE 32 +#define MAL_SND_PCM_FORMAT_S24_3BE 33 + +// snd_pcm_access_t +#define MAL_SND_PCM_ACCESS_MMAP_INTERLEAVED 0 +#define MAL_SND_PCM_ACCESS_MMAP_NONINTERLEAVED 1 +#define MAL_SND_PCM_ACCESS_MMAP_COMPLEX 2 +#define MAL_SND_PCM_ACCESS_RW_INTERLEAVED 3 +#define MAL_SND_PCM_ACCESS_RW_NONINTERLEAVED 4 + +// Channel positions. +#define MAL_SND_CHMAP_UNKNOWN 0 +#define MAL_SND_CHMAP_NA 1 +#define MAL_SND_CHMAP_MONO 2 +#define MAL_SND_CHMAP_FL 3 +#define MAL_SND_CHMAP_FR 4 +#define MAL_SND_CHMAP_RL 5 +#define MAL_SND_CHMAP_RR 6 +#define MAL_SND_CHMAP_FC 7 +#define MAL_SND_CHMAP_LFE 8 +#define MAL_SND_CHMAP_SL 9 +#define MAL_SND_CHMAP_SR 10 +#define MAL_SND_CHMAP_RC 11 +#define MAL_SND_CHMAP_FLC 12 +#define MAL_SND_CHMAP_FRC 13 +#define MAL_SND_CHMAP_RLC 14 +#define MAL_SND_CHMAP_RRC 15 +#define MAL_SND_CHMAP_FLW 16 +#define MAL_SND_CHMAP_FRW 17 +#define MAL_SND_CHMAP_FLH 18 +#define MAL_SND_CHMAP_FCH 19 +#define MAL_SND_CHMAP_FRH 20 +#define MAL_SND_CHMAP_TC 21 +#define MAL_SND_CHMAP_TFL 22 +#define MAL_SND_CHMAP_TFR 23 +#define MAL_SND_CHMAP_TFC 24 +#define MAL_SND_CHMAP_TRL 25 +#define MAL_SND_CHMAP_TRR 26 +#define MAL_SND_CHMAP_TRC 27 +#define MAL_SND_CHMAP_TFLC 28 +#define MAL_SND_CHMAP_TFRC 29 +#define MAL_SND_CHMAP_TSL 30 +#define MAL_SND_CHMAP_TSR 31 +#define MAL_SND_CHMAP_LLFE 32 +#define MAL_SND_CHMAP_RLFE 33 +#define MAL_SND_CHMAP_BC 34 +#define MAL_SND_CHMAP_BLC 35 +#define MAL_SND_CHMAP_BRC 36 + +// Open mode flags. +#define MAL_SND_PCM_NO_AUTO_RESAMPLE 0x00010000 +#define MAL_SND_PCM_NO_AUTO_CHANNELS 0x00020000 +#define MAL_SND_PCM_NO_AUTO_FORMAT 0x00040000 +#endif + +typedef int (* mal_snd_pcm_open_proc) (mal_snd_pcm_t **pcm, const char *name, mal_snd_pcm_stream_t stream, int mode); +typedef int (* mal_snd_pcm_close_proc) (mal_snd_pcm_t *pcm); +typedef size_t (* mal_snd_pcm_hw_params_sizeof_proc) (void); +typedef int (* mal_snd_pcm_hw_params_any_proc) (mal_snd_pcm_t *pcm, mal_snd_pcm_hw_params_t *params); +typedef int (* mal_snd_pcm_hw_params_set_format_proc) (mal_snd_pcm_t *pcm, mal_snd_pcm_hw_params_t *params, mal_snd_pcm_format_t val); +typedef int (* mal_snd_pcm_hw_params_set_format_first_proc) (mal_snd_pcm_t *pcm, mal_snd_pcm_hw_params_t *params, mal_snd_pcm_format_t *format); +typedef void (* mal_snd_pcm_hw_params_get_format_mask_proc) (mal_snd_pcm_hw_params_t *params, mal_snd_pcm_format_mask_t *mask); +typedef int (* mal_snd_pcm_hw_params_set_channels_near_proc) (mal_snd_pcm_t *pcm, mal_snd_pcm_hw_params_t *params, unsigned int *val); +typedef int (* mal_snd_pcm_hw_params_set_rate_resample_proc) (mal_snd_pcm_t *pcm, mal_snd_pcm_hw_params_t *params, unsigned int val); +typedef int (* mal_snd_pcm_hw_params_set_rate_near_proc) (mal_snd_pcm_t *pcm, mal_snd_pcm_hw_params_t *params, unsigned int *val, int *dir); +typedef int (* mal_snd_pcm_hw_params_set_buffer_size_near_proc)(mal_snd_pcm_t *pcm, mal_snd_pcm_hw_params_t *params, mal_snd_pcm_uframes_t *val); +typedef int (* mal_snd_pcm_hw_params_set_periods_near_proc) (mal_snd_pcm_t *pcm, mal_snd_pcm_hw_params_t *params, unsigned int *val, int *dir); +typedef int (* mal_snd_pcm_hw_params_set_access_proc) (mal_snd_pcm_t *pcm, mal_snd_pcm_hw_params_t *params, mal_snd_pcm_access_t _access); +typedef int (* mal_snd_pcm_hw_params_get_format_proc) (const mal_snd_pcm_hw_params_t *params, mal_snd_pcm_format_t *format); +typedef int (* mal_snd_pcm_hw_params_get_channels_proc) (const mal_snd_pcm_hw_params_t *params, unsigned int *val); +typedef int (* mal_snd_pcm_hw_params_get_rate_proc) (const mal_snd_pcm_hw_params_t *params, unsigned int *rate, int *dir); +typedef int (* mal_snd_pcm_hw_params_get_buffer_size_proc) (const mal_snd_pcm_hw_params_t *params, mal_snd_pcm_uframes_t *val); +typedef int (* mal_snd_pcm_hw_params_get_periods_proc) (const mal_snd_pcm_hw_params_t *params, unsigned int *val, int *dir); +typedef int (* mal_snd_pcm_hw_params_get_access_proc) (const mal_snd_pcm_hw_params_t *params, mal_snd_pcm_access_t *_access); +typedef int (* mal_snd_pcm_hw_params_proc) (mal_snd_pcm_t *pcm, mal_snd_pcm_hw_params_t *params); +typedef size_t (* mal_snd_pcm_sw_params_sizeof_proc) (void); +typedef int (* mal_snd_pcm_sw_params_current_proc) (mal_snd_pcm_t *pcm, mal_snd_pcm_sw_params_t *params); +typedef int (* mal_snd_pcm_sw_params_set_avail_min_proc) (mal_snd_pcm_t *pcm, mal_snd_pcm_sw_params_t *params, mal_snd_pcm_uframes_t val); +typedef int (* mal_snd_pcm_sw_params_set_start_threshold_proc) (mal_snd_pcm_t *pcm, mal_snd_pcm_sw_params_t *params, mal_snd_pcm_uframes_t val); +typedef int (* mal_snd_pcm_sw_params_proc) (mal_snd_pcm_t *pcm, mal_snd_pcm_sw_params_t *params); +typedef size_t (* mal_snd_pcm_format_mask_sizeof_proc) (void); +typedef int (* mal_snd_pcm_format_mask_test_proc) (const mal_snd_pcm_format_mask_t *mask, mal_snd_pcm_format_t val); +typedef mal_snd_pcm_chmap_t * (* mal_snd_pcm_get_chmap_proc) (mal_snd_pcm_t *pcm); +typedef int (* mal_snd_pcm_prepare_proc) (mal_snd_pcm_t *pcm); +typedef int (* mal_snd_pcm_start_proc) (mal_snd_pcm_t *pcm); +typedef int (* mal_snd_pcm_drop_proc) (mal_snd_pcm_t *pcm); +typedef int (* mal_snd_device_name_hint_proc) (int card, const char *iface, void ***hints); +typedef char * (* mal_snd_device_name_get_hint_proc) (const void *hint, const char *id); +typedef int (* mal_snd_card_get_index_proc) (const char *name); +typedef int (* mal_snd_device_name_free_hint_proc) (void **hints); +typedef int (* mal_snd_pcm_mmap_begin_proc) (mal_snd_pcm_t *pcm, const mal_snd_pcm_channel_area_t **areas, mal_snd_pcm_uframes_t *offset, mal_snd_pcm_uframes_t *frames); +typedef mal_snd_pcm_sframes_t (* mal_snd_pcm_mmap_commit_proc) (mal_snd_pcm_t *pcm, mal_snd_pcm_uframes_t offset, mal_snd_pcm_uframes_t frames); +typedef int (* mal_snd_pcm_recover_proc) (mal_snd_pcm_t *pcm, int err, int silent); +typedef mal_snd_pcm_sframes_t (* mal_snd_pcm_readi_proc) (mal_snd_pcm_t *pcm, void *buffer, mal_snd_pcm_uframes_t size); +typedef mal_snd_pcm_sframes_t (* mal_snd_pcm_writei_proc) (mal_snd_pcm_t *pcm, const void *buffer, mal_snd_pcm_uframes_t size); +typedef mal_snd_pcm_sframes_t (* mal_snd_pcm_avail_proc) (mal_snd_pcm_t *pcm); +typedef mal_snd_pcm_sframes_t (* mal_snd_pcm_avail_update_proc) (mal_snd_pcm_t *pcm); +typedef int (* mal_snd_pcm_wait_proc) (mal_snd_pcm_t *pcm, int timeout); +typedef int (* mal_snd_pcm_info_proc) (mal_snd_pcm_t *pcm, mal_snd_pcm_info_t* info); +typedef size_t (* mal_snd_pcm_info_sizeof_proc) (); +typedef const char* (* mal_snd_pcm_info_get_name_proc) (const mal_snd_pcm_info_t* info); +typedef int (* mal_snd_config_update_free_global_proc) (); + +mal_snd_pcm_format_t g_mal_ALSAFormats[] = { + MAL_SND_PCM_FORMAT_UNKNOWN, // mal_format_unknown + MAL_SND_PCM_FORMAT_U8, // mal_format_u8 + MAL_SND_PCM_FORMAT_S16_LE, // mal_format_s16 + MAL_SND_PCM_FORMAT_S24_3LE, // mal_format_s24 + //MAL_SND_PCM_FORMAT_S24_LE, // mal_format_s24_32 + MAL_SND_PCM_FORMAT_S32_LE, // mal_format_s32 + MAL_SND_PCM_FORMAT_FLOAT_LE // mal_format_f32 +}; + +// This array specifies each of the common devices that can be used for both playback and capture. +const char* g_malCommonDeviceNamesALSA[] = { + "default", + "null", + "pulse", + "jack" +}; + +// This array allows us to blacklist specific playback devices. +const char* g_malBlacklistedPlaybackDeviceNamesALSA[] = { + "" +}; + +// This array allows us to blacklist specific capture devices. +const char* g_malBlacklistedCaptureDeviceNamesALSA[] = { + "" +}; + // This array allows mini_al to control device-specific default buffer sizes. This uses a scaling factor. Order is important. If // any part of the string is present in the device's name, the associated scale will be used. -struct +static struct { const char* name; float scale; @@ -5527,7 +7778,7 @@ struct {"bcm2835 ALSA", 20} }; -static float mal_find_default_buffer_size_scale__alsa(const char* deviceName) +float mal_find_default_buffer_size_scale__alsa(const char* deviceName) { if (deviceName == NULL) { return 1; @@ -5542,78 +7793,22 @@ static float mal_find_default_buffer_size_scale__alsa(const char* deviceName) return 1; } - -typedef int (* mal_snd_pcm_open_proc) (snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode); -typedef int (* mal_snd_pcm_close_proc) (snd_pcm_t *pcm); -typedef size_t (* mal_snd_pcm_hw_params_sizeof_proc) (void); -typedef int (* mal_snd_pcm_hw_params_any_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params); -typedef int (* mal_snd_pcm_hw_params_set_format_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val); -typedef int (* mal_snd_pcm_hw_params_set_format_first_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t *format); -typedef void (* mal_snd_pcm_hw_params_get_format_mask_proc) (snd_pcm_hw_params_t *params, snd_pcm_format_mask_t *mask); -typedef int (* mal_snd_pcm_hw_params_set_channels_near_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val); -typedef int (* mal_snd_pcm_hw_params_set_rate_resample_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val); -typedef int (* mal_snd_pcm_hw_params_set_rate_near_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir); -typedef int (* mal_snd_pcm_hw_params_set_buffer_size_near_proc)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val); -typedef int (* mal_snd_pcm_hw_params_set_periods_near_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir); -typedef int (* mal_snd_pcm_hw_params_set_access_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t _access); -typedef int (* mal_snd_pcm_hw_params_get_format_proc) (snd_pcm_hw_params_t *params, snd_pcm_format_t *format); -typedef int (* mal_snd_pcm_hw_params_get_channels_proc) (snd_pcm_hw_params_t *params, unsigned int *val); -typedef int (* mal_snd_pcm_hw_params_get_rate_proc) (snd_pcm_hw_params_t *params, unsigned int *rate, int *dir); -typedef int (* mal_snd_pcm_hw_params_get_buffer_size_proc) (snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val); -typedef int (* mal_snd_pcm_hw_params_get_periods_proc) (snd_pcm_hw_params_t *params, unsigned int *val, int *dir); -typedef int (* mal_snd_pcm_hw_params_get_access_proc) (snd_pcm_hw_params_t *params, snd_pcm_access_t *_access); -typedef int (* mal_snd_pcm_hw_params_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params); -typedef size_t (* mal_snd_pcm_sw_params_sizeof_proc) (void); -typedef int (* mal_snd_pcm_sw_params_current_proc) (snd_pcm_t *pcm, snd_pcm_sw_params_t *params); -typedef int (* mal_snd_pcm_sw_params_set_avail_min_proc) (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val); -typedef int (* mal_snd_pcm_sw_params_set_start_threshold_proc) (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val); -typedef int (* mal_snd_pcm_sw_params_proc) (snd_pcm_t *pcm, snd_pcm_sw_params_t *params); -typedef size_t (* mal_snd_pcm_format_mask_sizeof_proc) (void); -typedef int (* mal_snd_pcm_format_mask_test_proc) (const snd_pcm_format_mask_t *mask, snd_pcm_format_t val); -typedef snd_pcm_chmap_t * (* mal_snd_pcm_get_chmap_proc) (snd_pcm_t *pcm); -typedef int (* mal_snd_pcm_prepare_proc) (snd_pcm_t *pcm); -typedef int (* mal_snd_pcm_start_proc) (snd_pcm_t *pcm); -typedef int (* mal_snd_pcm_drop_proc) (snd_pcm_t *pcm); -typedef int (* mal_snd_device_name_hint_proc) (int card, const char *iface, void ***hints); -typedef char * (* mal_snd_device_name_get_hint_proc) (const void *hint, const char *id); -typedef int (* mal_snd_card_get_index_proc) (const char *name); -typedef int (* mal_snd_device_name_free_hint_proc) (void **hints); -typedef int (* mal_snd_pcm_mmap_begin_proc) (snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames); -typedef snd_pcm_sframes_t (* mal_snd_pcm_mmap_commit_proc) (snd_pcm_t *pcm, snd_pcm_uframes_t offset, snd_pcm_uframes_t frames); -typedef int (* mal_snd_pcm_recover_proc) (snd_pcm_t *pcm, int err, int silent); -typedef snd_pcm_sframes_t (* mal_snd_pcm_readi_proc) (snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size); -typedef snd_pcm_sframes_t (* mal_snd_pcm_writei_proc) (snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size); -typedef snd_pcm_sframes_t (* mal_snd_pcm_avail_proc) (snd_pcm_t *pcm); -typedef snd_pcm_sframes_t (* mal_snd_pcm_avail_update_proc) (snd_pcm_t *pcm); -typedef int (* mal_snd_pcm_wait_proc) (snd_pcm_t *pcm, int timeout); -typedef int (* mal_snd_pcm_info) (snd_pcm_t *pcm, snd_pcm_info_t* info); -typedef size_t (* mal_snd_pcm_info_sizeof) (); -typedef const char* (* mal_snd_pcm_info_get_name) (const snd_pcm_info_t* info); - -static snd_pcm_format_t g_mal_ALSAFormats[] = { - SND_PCM_FORMAT_UNKNOWN, // mal_format_unknown - SND_PCM_FORMAT_U8, // mal_format_u8 - SND_PCM_FORMAT_S16_LE, // mal_format_s16 - SND_PCM_FORMAT_S24_3LE, // mal_format_s24 - SND_PCM_FORMAT_S32_LE, // mal_format_s32 - SND_PCM_FORMAT_FLOAT_LE // mal_format_f32 -}; - -snd_pcm_format_t mal_convert_mal_format_to_alsa_format(mal_format format) +mal_snd_pcm_format_t mal_convert_mal_format_to_alsa_format(mal_format format) { return g_mal_ALSAFormats[format]; } -mal_format mal_convert_alsa_format_to_mal_format(snd_pcm_format_t formatALSA) +mal_format mal_convert_alsa_format_to_mal_format(mal_snd_pcm_format_t formatALSA) { switch (formatALSA) { - case SND_PCM_FORMAT_U8: return mal_format_u8; - case SND_PCM_FORMAT_S16_LE: return mal_format_s16; - case SND_PCM_FORMAT_S24_3LE: return mal_format_s24; - case SND_PCM_FORMAT_S32_LE: return mal_format_s32; - case SND_PCM_FORMAT_FLOAT_LE: return mal_format_f32; - default: return mal_format_unknown; + case MAL_SND_PCM_FORMAT_U8: return mal_format_u8; + case MAL_SND_PCM_FORMAT_S16_LE: return mal_format_s16; + case MAL_SND_PCM_FORMAT_S24_3LE: return mal_format_s24; + //MAL_SND_PCM_FORMAT_S24_LE, return mal_format_s24_32 + case MAL_SND_PCM_FORMAT_S32_LE: return mal_format_s32; + case MAL_SND_PCM_FORMAT_FLOAT_LE: return mal_format_f32; + default: return mal_format_unknown; } } @@ -5621,106 +7816,83 @@ mal_channel mal_convert_alsa_channel_position_to_mal_channel(unsigned int alsaCh { switch (alsaChannelPos) { - case SND_CHMAP_FL: return MAL_CHANNEL_FRONT_LEFT; - case SND_CHMAP_FR: return MAL_CHANNEL_FRONT_RIGHT; - case SND_CHMAP_RL: return MAL_CHANNEL_BACK_LEFT; - case SND_CHMAP_RR: return MAL_CHANNEL_BACK_RIGHT; - case SND_CHMAP_FC: return MAL_CHANNEL_FRONT_CENTER; - case SND_CHMAP_LFE: return MAL_CHANNEL_LFE; - case SND_CHMAP_SL: return MAL_CHANNEL_SIDE_LEFT; - case SND_CHMAP_SR: return MAL_CHANNEL_SIDE_RIGHT; - case SND_CHMAP_RC: return MAL_CHANNEL_BACK_CENTER; - case SND_CHMAP_FLC: return MAL_CHANNEL_FRONT_LEFT_CENTER; - case SND_CHMAP_FRC: return MAL_CHANNEL_FRONT_RIGHT_CENTER; - case SND_CHMAP_RLC: return 0; - case SND_CHMAP_RRC: return 0; - case SND_CHMAP_FLW: return 0; - case SND_CHMAP_FRW: return 0; - case SND_CHMAP_FLH: return 0; - case SND_CHMAP_FCH: return 0; - case SND_CHMAP_FRH: return 0; - case SND_CHMAP_TC: return MAL_CHANNEL_TOP_CENTER; - case SND_CHMAP_TFL: return MAL_CHANNEL_TOP_FRONT_LEFT; - case SND_CHMAP_TFR: return MAL_CHANNEL_TOP_FRONT_RIGHT; - case SND_CHMAP_TFC: return MAL_CHANNEL_TOP_FRONT_CENTER; - case SND_CHMAP_TRL: return MAL_CHANNEL_TOP_BACK_LEFT; - case SND_CHMAP_TRR: return MAL_CHANNEL_TOP_BACK_RIGHT; - case SND_CHMAP_TRC: return MAL_CHANNEL_TOP_BACK_CENTER; + case MAL_SND_CHMAP_MONO: return MAL_CHANNEL_MONO; + case MAL_SND_CHMAP_FL: return MAL_CHANNEL_FRONT_LEFT; + case MAL_SND_CHMAP_FR: return MAL_CHANNEL_FRONT_RIGHT; + case MAL_SND_CHMAP_RL: return MAL_CHANNEL_BACK_LEFT; + case MAL_SND_CHMAP_RR: return MAL_CHANNEL_BACK_RIGHT; + case MAL_SND_CHMAP_FC: return MAL_CHANNEL_FRONT_CENTER; + case MAL_SND_CHMAP_LFE: return MAL_CHANNEL_LFE; + case MAL_SND_CHMAP_SL: return MAL_CHANNEL_SIDE_LEFT; + case MAL_SND_CHMAP_SR: return MAL_CHANNEL_SIDE_RIGHT; + case MAL_SND_CHMAP_RC: return MAL_CHANNEL_BACK_CENTER; + case MAL_SND_CHMAP_FLC: return MAL_CHANNEL_FRONT_LEFT_CENTER; + case MAL_SND_CHMAP_FRC: return MAL_CHANNEL_FRONT_RIGHT_CENTER; + case MAL_SND_CHMAP_RLC: return 0; + case MAL_SND_CHMAP_RRC: return 0; + case MAL_SND_CHMAP_FLW: return 0; + case MAL_SND_CHMAP_FRW: return 0; + case MAL_SND_CHMAP_FLH: return 0; + case MAL_SND_CHMAP_FCH: return 0; + case MAL_SND_CHMAP_FRH: return 0; + case MAL_SND_CHMAP_TC: return MAL_CHANNEL_TOP_CENTER; + case MAL_SND_CHMAP_TFL: return MAL_CHANNEL_TOP_FRONT_LEFT; + case MAL_SND_CHMAP_TFR: return MAL_CHANNEL_TOP_FRONT_RIGHT; + case MAL_SND_CHMAP_TFC: return MAL_CHANNEL_TOP_FRONT_CENTER; + case MAL_SND_CHMAP_TRL: return MAL_CHANNEL_TOP_BACK_LEFT; + case MAL_SND_CHMAP_TRR: return MAL_CHANNEL_TOP_BACK_RIGHT; + case MAL_SND_CHMAP_TRC: return MAL_CHANNEL_TOP_BACK_CENTER; default: break; } return 0; } -mal_result mal_context_init__alsa(mal_context* pContext) +mal_bool32 mal_is_common_device_name__alsa(const char* name) { - mal_assert(pContext != NULL); - - pContext->alsa.asoundSO = mal_dlopen("libasound.so"); - if (pContext->alsa.asoundSO == NULL) { - return MAL_NO_BACKEND; + for (size_t iName = 0; iName < mal_countof(g_malCommonDeviceNamesALSA); ++iName) { + if (mal_strcmp(name, g_malCommonDeviceNamesALSA[iName]) == 0) { + return MAL_TRUE; + } } - pContext->alsa.snd_pcm_open = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_open"); - pContext->alsa.snd_pcm_close = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_close"); - pContext->alsa.snd_pcm_hw_params_sizeof = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_sizeof"); - pContext->alsa.snd_pcm_hw_params_any = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_any"); - pContext->alsa.snd_pcm_hw_params_set_format = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_format"); - pContext->alsa.snd_pcm_hw_params_set_format_first = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_format_first"); - pContext->alsa.snd_pcm_hw_params_get_format_mask = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_format_mask"); - pContext->alsa.snd_pcm_hw_params_set_channels_near = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_channels_near"); - pContext->alsa.snd_pcm_hw_params_set_rate_resample = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_rate_resample"); - pContext->alsa.snd_pcm_hw_params_set_rate_near = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_rate_near"); - pContext->alsa.snd_pcm_hw_params_set_buffer_size_near = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_buffer_size_near"); - pContext->alsa.snd_pcm_hw_params_set_periods_near = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_periods_near"); - pContext->alsa.snd_pcm_hw_params_set_access = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_access"); - pContext->alsa.snd_pcm_hw_params_get_format = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_format"); - pContext->alsa.snd_pcm_hw_params_get_channels = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_channels"); - pContext->alsa.snd_pcm_hw_params_get_rate = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_rate"); - pContext->alsa.snd_pcm_hw_params_get_buffer_size = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_buffer_size"); - pContext->alsa.snd_pcm_hw_params_get_periods = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_periods"); - pContext->alsa.snd_pcm_hw_params_get_access = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_access"); - pContext->alsa.snd_pcm_hw_params = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params"); - pContext->alsa.snd_pcm_sw_params_sizeof = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params_sizeof"); - pContext->alsa.snd_pcm_sw_params_current = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params_current"); - pContext->alsa.snd_pcm_sw_params_set_avail_min = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params_set_avail_min"); - pContext->alsa.snd_pcm_sw_params_set_start_threshold = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params_set_start_threshold"); - pContext->alsa.snd_pcm_sw_params = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params"); - pContext->alsa.snd_pcm_format_mask_sizeof = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_format_mask_sizeof"); - pContext->alsa.snd_pcm_format_mask_test = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_format_mask_test"); - pContext->alsa.snd_pcm_get_chmap = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_get_chmap"); - pContext->alsa.snd_pcm_prepare = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_prepare"); - pContext->alsa.snd_pcm_start = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_start"); - pContext->alsa.snd_pcm_drop = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_drop"); - pContext->alsa.snd_device_name_hint = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_device_name_hint"); - pContext->alsa.snd_device_name_get_hint = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_device_name_get_hint"); - pContext->alsa.snd_card_get_index = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_card_get_index"); - pContext->alsa.snd_device_name_free_hint = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_device_name_free_hint"); - pContext->alsa.snd_pcm_mmap_begin = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_mmap_begin"); - pContext->alsa.snd_pcm_mmap_commit = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_mmap_commit"); - pContext->alsa.snd_pcm_recover = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_recover"); - pContext->alsa.snd_pcm_readi = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_readi"); - pContext->alsa.snd_pcm_writei = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_writei"); - pContext->alsa.snd_pcm_avail = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_avail"); - pContext->alsa.snd_pcm_avail_update = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_avail_update"); - pContext->alsa.snd_pcm_wait = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_wait"); - pContext->alsa.snd_pcm_info = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_info"); - pContext->alsa.snd_pcm_info_sizeof = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_info_sizeof"); - pContext->alsa.snd_pcm_info_get_name = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_info_get_name"); - - return MAL_SUCCESS; + return MAL_FALSE; } -mal_result mal_context_uninit__alsa(mal_context* pContext) + +mal_bool32 mal_is_playback_device_blacklisted__alsa(const char* name) { - mal_assert(pContext != NULL); - mal_assert(pContext->backend == mal_backend_alsa); + for (size_t iName = 0; iName < mal_countof(g_malBlacklistedPlaybackDeviceNamesALSA); ++iName) { + if (mal_strcmp(name, g_malBlacklistedPlaybackDeviceNamesALSA[iName]) == 0) { + return MAL_TRUE; + } + } - (void)pContext; - return MAL_SUCCESS; + return MAL_FALSE; } -static const char* mal_find_char(const char* str, char c, int* index) +mal_bool32 mal_is_capture_device_blacklisted__alsa(const char* name) +{ + for (size_t iName = 0; iName < mal_countof(g_malBlacklistedCaptureDeviceNamesALSA); ++iName) { + if (mal_strcmp(name, g_malBlacklistedCaptureDeviceNamesALSA[iName]) == 0) { + return MAL_TRUE; + } + } + + return MAL_FALSE; +} + +mal_bool32 mal_is_device_blacklisted__alsa(mal_device_type deviceType, const char* name) +{ + if (deviceType == mal_device_type_playback) { + return mal_is_playback_device_blacklisted__alsa(name); + } else { + return mal_is_capture_device_blacklisted__alsa(name); + } +} + + +const char* mal_find_char(const char* str, char c, int* index) { int i = 0; for (;;) { @@ -5743,264 +7915,7 @@ static const char* mal_find_char(const char* str, char c, int* index) return NULL; } -// Waits for a number of frames to become available for either capture or playback. The return -// value is the number of frames available. -// -// This will return early if the main loop is broken with mal_device__break_main_loop(). -static mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice, mal_bool32* pRequiresRestart) -{ - mal_assert(pDevice != NULL); - - if (pRequiresRestart) *pRequiresRestart = MAL_FALSE; - - mal_uint32 periodSizeInFrames = pDevice->bufferSizeInFrames / pDevice->periods; - - while (!pDevice->alsa.breakFromMainLoop) { - // Wait for something to become available. The timeout should not affect latency - it's only used to break from the wait - // so we can check whether or not the device has been stopped. - const int timeoutInMilliseconds = 10; - int waitResult = ((mal_snd_pcm_wait_proc)pDevice->pContext->alsa.snd_pcm_wait)((snd_pcm_t*)pDevice->alsa.pPCM, timeoutInMilliseconds); - if (waitResult < 0) { - if (waitResult == -EPIPE) { - if (((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((snd_pcm_t*)pDevice->alsa.pPCM, waitResult, MAL_TRUE) < 0) { - return 0; - } - - if (pRequiresRestart) *pRequiresRestart = MAL_TRUE; // A device recovery means a restart for mmap mode. - } - } - - if (pDevice->alsa.breakFromMainLoop) { - return 0; - } - - snd_pcm_sframes_t framesAvailable = ((mal_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((snd_pcm_t*)pDevice->alsa.pPCM); - if (framesAvailable < 0) { - if (framesAvailable == -EPIPE) { - if (((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((snd_pcm_t*)pDevice->alsa.pPCM, framesAvailable, MAL_TRUE) < 0) { - return 0; - } - - if (pRequiresRestart) *pRequiresRestart = MAL_TRUE; // A device recovery means a restart for mmap mode. - - // Try again, but if it fails this time just return an error. - framesAvailable = ((mal_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((snd_pcm_t*)pDevice->alsa.pPCM); - if (framesAvailable < 0) { - return 0; - } - } - } - - // Keep the returned number of samples consistent and based on the period size. - if (framesAvailable >= periodSizeInFrames) { - return periodSizeInFrames; - } - } - - // We'll get here if the loop was terminated. Just return whatever's available. - snd_pcm_sframes_t framesAvailable = ((mal_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((snd_pcm_t*)pDevice->alsa.pPCM); - if (framesAvailable < 0) { - return 0; - } - - return framesAvailable; -} - -static mal_bool32 mal_device_write__alsa(mal_device* pDevice) -{ - mal_assert(pDevice != NULL); - if (!mal_device_is_started(pDevice) && mal_device__get_state(pDevice) != MAL_STATE_STARTING) { - return MAL_FALSE; - } - if (pDevice->alsa.breakFromMainLoop) { - return MAL_FALSE; - } - - - if (pDevice->alsa.isUsingMMap) { - // mmap. - mal_bool32 requiresRestart; - mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, &requiresRestart); - if (framesAvailable == 0) { - return MAL_FALSE; - } - - // Don't bother asking the client for more audio data if we're just stopping the device anyway. - if (pDevice->alsa.breakFromMainLoop) { - return MAL_FALSE; - } - - const snd_pcm_channel_area_t* pAreas; - snd_pcm_uframes_t mappedOffset; - snd_pcm_uframes_t mappedFrames = framesAvailable; - while (framesAvailable > 0) { - int result = ((mal_snd_pcm_mmap_begin_proc)pDevice->pContext->alsa.snd_pcm_mmap_begin)((snd_pcm_t*)pDevice->alsa.pPCM, &pAreas, &mappedOffset, &mappedFrames); - if (result < 0) { - return MAL_FALSE; - } - - if (mappedFrames > 0) { - void* pBuffer = (mal_uint8*)pAreas[0].addr + ((pAreas[0].first + (mappedOffset * pAreas[0].step)) / 8); - mal_device__read_frames_from_client(pDevice, mappedFrames, pBuffer); - } - - result = ((mal_snd_pcm_mmap_commit_proc)pDevice->pContext->alsa.snd_pcm_mmap_commit)((snd_pcm_t*)pDevice->alsa.pPCM, mappedOffset, mappedFrames); - if (result < 0 || (snd_pcm_uframes_t)result != mappedFrames) { - ((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((snd_pcm_t*)pDevice->alsa.pPCM, result, MAL_TRUE); - return MAL_FALSE; - } - - if (requiresRestart) { - if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((snd_pcm_t*)pDevice->alsa.pPCM) < 0) { - return MAL_FALSE; - } - } - - framesAvailable -= mappedFrames; - } - } else { - // readi/writei. - while (!pDevice->alsa.breakFromMainLoop) { - mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, NULL); - if (framesAvailable == 0) { - continue; - } - - // Don't bother asking the client for more audio data if we're just stopping the device anyway. - if (pDevice->alsa.breakFromMainLoop) { - return MAL_FALSE; - } - - mal_device__read_frames_from_client(pDevice, framesAvailable, pDevice->alsa.pIntermediaryBuffer); - - snd_pcm_sframes_t framesWritten = ((mal_snd_pcm_writei_proc)pDevice->pContext->alsa.snd_pcm_writei)((snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); - if (framesWritten < 0) { - if (framesWritten == -EAGAIN) { - continue; // Just keep trying... - } else if (framesWritten == -EPIPE) { - // Underrun. Just recover and try writing again. - if (((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((snd_pcm_t*)pDevice->alsa.pPCM, framesWritten, MAL_TRUE) < 0) { - mal_post_error(pDevice, "[ALSA] Failed to recover device after underrun.", MAL_ALSA_FAILED_TO_RECOVER_DEVICE); - return MAL_FALSE; - } - - framesWritten = ((mal_snd_pcm_writei_proc)pDevice->pContext->alsa.snd_pcm_writei)((snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); - if (framesWritten < 0) { - mal_post_error(pDevice, "[ALSA] Failed to write data to the internal device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); - return MAL_FALSE; - } - - break; // Success. - } else { - mal_post_error(pDevice, "[ALSA] snd_pcm_writei() failed when writing initial data.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); - return MAL_FALSE; - } - } else { - break; // Success. - } - } - } - - return MAL_TRUE; -} - -static mal_bool32 mal_device_read__alsa(mal_device* pDevice) -{ - mal_assert(pDevice != NULL); - if (!mal_device_is_started(pDevice)) { - return MAL_FALSE; - } - if (pDevice->alsa.breakFromMainLoop) { - return MAL_FALSE; - } - - mal_uint32 framesToSend = 0; - void* pBuffer = NULL; - if (pDevice->alsa.pIntermediaryBuffer == NULL) { - // mmap. - mal_bool32 requiresRestart; - mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, &requiresRestart); - if (framesAvailable == 0) { - return MAL_FALSE; - } - - const snd_pcm_channel_area_t* pAreas; - snd_pcm_uframes_t mappedOffset; - snd_pcm_uframes_t mappedFrames = framesAvailable; - while (framesAvailable > 0) { - int result = ((mal_snd_pcm_mmap_begin_proc)pDevice->pContext->alsa.snd_pcm_mmap_begin)((snd_pcm_t*)pDevice->alsa.pPCM, &pAreas, &mappedOffset, &mappedFrames); - if (result < 0) { - return MAL_FALSE; - } - - if (mappedFrames > 0) { - void* pBuffer = (mal_uint8*)pAreas[0].addr + ((pAreas[0].first + (mappedOffset * pAreas[0].step)) / 8); - mal_device__send_frames_to_client(pDevice, mappedFrames, pBuffer); - } - - result = ((mal_snd_pcm_mmap_commit_proc)pDevice->pContext->alsa.snd_pcm_mmap_commit)((snd_pcm_t*)pDevice->alsa.pPCM, mappedOffset, mappedFrames); - if (result < 0 || (snd_pcm_uframes_t)result != mappedFrames) { - ((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((snd_pcm_t*)pDevice->alsa.pPCM, result, MAL_TRUE); - return MAL_FALSE; - } - - if (requiresRestart) { - if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((snd_pcm_t*)pDevice->alsa.pPCM) < 0) { - return MAL_FALSE; - } - } - - framesAvailable -= mappedFrames; - } - } else { - // readi/writei. - snd_pcm_sframes_t framesRead = 0; - while (!pDevice->alsa.breakFromMainLoop) { - mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, NULL); - if (framesAvailable == 0) { - continue; - } - - framesRead = ((mal_snd_pcm_readi_proc)pDevice->pContext->alsa.snd_pcm_readi)((snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); - if (framesRead < 0) { - if (framesRead == -EAGAIN) { - continue; // Just keep trying... - } else if (framesRead == -EPIPE) { - // Overrun. Just recover and try reading again. - if (((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((snd_pcm_t*)pDevice->alsa.pPCM, framesRead, MAL_TRUE) < 0) { - mal_post_error(pDevice, "[ALSA] Failed to recover device after overrun.", MAL_ALSA_FAILED_TO_RECOVER_DEVICE); - return MAL_FALSE; - } - - framesRead = ((mal_snd_pcm_readi_proc)pDevice->pContext->alsa.snd_pcm_readi)((snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); - if (framesRead < 0) { - mal_post_error(pDevice, "[ALSA] Failed to read data from the internal device.", MAL_FAILED_TO_READ_DATA_FROM_DEVICE); - return MAL_FALSE; - } - - break; // Success. - } else { - return MAL_FALSE; - } - } else { - break; // Success. - } - } - - framesToSend = framesRead; - pBuffer = pDevice->alsa.pIntermediaryBuffer; - } - - if (framesToSend > 0) { - mal_device__send_frames_to_client(pDevice, framesToSend, pBuffer); - } - - return MAL_TRUE; -} - - - -static mal_bool32 mal_is_device_name_in_hw_format__alsa(const char* hwid) +mal_bool32 mal_is_device_name_in_hw_format__alsa(const char* hwid) { // This function is just checking whether or not hwid is in "hw:%d,%d" format. @@ -6041,7 +7956,7 @@ static mal_bool32 mal_is_device_name_in_hw_format__alsa(const char* hwid) return MAL_TRUE; } -static int mal_convert_device_name_to_hw_format__alsa(mal_context* pContext, char* dst, size_t dstSize, const char* src) // Returns 0 on success, non-0 on error. +int mal_convert_device_name_to_hw_format__alsa(mal_context* pContext, char* dst, size_t dstSize, const char* src) // Returns 0 on success, non-0 on error. { // src should look something like this: "hw:CARD=I82801AAICH,DEV=0" @@ -6098,7 +8013,7 @@ static int mal_convert_device_name_to_hw_format__alsa(mal_context* pContext, cha return 0; } -static mal_bool32 mal_does_id_exist_in_list__alsa(mal_device_id* pUniqueIDs, mal_uint32 count, const char* pHWID) +mal_bool32 mal_does_id_exist_in_list__alsa(mal_device_id* pUniqueIDs, mal_uint32 count, const char* pHWID) { mal_assert(pHWID != NULL); @@ -6111,15 +8026,29 @@ static mal_bool32 mal_does_id_exist_in_list__alsa(mal_device_id* pUniqueIDs, mal return MAL_FALSE; } -static mal_result mal_enumerate_devices__alsa(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) + +mal_bool32 mal_context_is_device_id_equal__alsa(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) { + mal_assert(pContext != NULL); + mal_assert(pID0 != NULL); + mal_assert(pID1 != NULL); (void)pContext; - mal_uint32 infoSize = *pCount; - *pCount = 0; + return mal_strcmp(pID0->alsa, pID1->alsa) == 0; +} + +mal_result mal_context_enumerate_devices__alsa(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +{ + mal_assert(pContext != NULL); + mal_assert(callback != NULL); + + mal_bool32 cbResult = MAL_TRUE; + + mal_mutex_lock(&pContext->alsa.internalDeviceEnumLock); char** ppDeviceHints; if (((mal_snd_device_name_hint_proc)pContext->alsa.snd_device_name_hint)(-1, "pcm", (void***)&ppDeviceHints) < 0) { + mal_mutex_unlock(&pContext->alsa.internalDeviceEnumLock); return MAL_NO_BACKEND; } @@ -6132,117 +8061,120 @@ static mal_result mal_enumerate_devices__alsa(mal_context* pContext, mal_device_ char* DESC = ((mal_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "DESC"); char* IOID = ((mal_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "IOID"); - // Only include devices if they are of the correct type. Special cases for "default", "null" and "pulse" - these are included for both playback and capture - // regardless of the IOID setting. - mal_bool32 includeThisDevice = MAL_FALSE; - if (strcmp(NAME, "default") == 0 || strcmp(NAME, "pulse") == 0 || strcmp(NAME, "null") == 0) { - includeThisDevice = MAL_TRUE; + mal_device_type deviceType = mal_device_type_playback; + if ((IOID == NULL || mal_strcmp(IOID, "Output") == 0)) { + deviceType = mal_device_type_playback; + } + if ((IOID != NULL && mal_strcmp(IOID, "Input" ) == 0)) { + deviceType = mal_device_type_capture; + } - // Exclude the "null" device if requested. - if (strcmp(NAME, "null") == 0 && pContext->config.alsa.excludeNullDevice) { - includeThisDevice = MAL_FALSE; + mal_bool32 stopEnumeration = MAL_FALSE; +#if 0 + printf("NAME: %s\n", NAME); + printf("DESC: %s\n", DESC); + printf("IOID: %s\n", IOID); + + char hwid2[256]; + mal_convert_device_name_to_hw_format__alsa(pContext, hwid2, sizeof(hwid2), NAME); + printf("DEVICE ID: %s\n\n", hwid2); +#endif + + char hwid[sizeof(pUniqueIDs->alsa)]; + if (NAME != NULL) { + if (pContext->config.alsa.useVerboseDeviceEnumeration) { + // Verbose mode. Use the name exactly as-is. + mal_strncpy_s(hwid, sizeof(hwid), NAME, (size_t)-1); + } else { + // Simplified mode. Use ":%d,%d" format. + if (mal_convert_device_name_to_hw_format__alsa(pContext, hwid, sizeof(hwid), NAME) == 0) { + // At this point, hwid looks like "hw:0,0". In simplified enumeration mode, we actually want to strip off the + // plugin name so it looks like ":0,0". The reason for this is that this special format is detected at device + // initialization time and is used as an indicator to try and use the most appropriate plugin depending on the + // device type and sharing mode. + char* dst = hwid; + char* src = hwid+2; + while ((*dst++ = *src++)); + } else { + // Conversion to "hw:%d,%d" failed. Just use the name as-is. + mal_strncpy_s(hwid, sizeof(hwid), NAME, (size_t)-1); + } + + if (mal_does_id_exist_in_list__alsa(pUniqueIDs, uniqueIDCount, hwid)) { + goto next_device; // The device has already been enumerated. Move on to the next one. + } else { + // The device has not yet been enumerated. Make sure it's added to our list so that it's not enumerated again. + mal_device_id* pNewUniqueIDs = (mal_device_id*)mal_realloc(pUniqueIDs, sizeof(*pUniqueIDs) * (uniqueIDCount + 1)); + if (pNewUniqueIDs == NULL) { + goto next_device; // Failed to allocate memory. + } + + pUniqueIDs = pNewUniqueIDs; + mal_copy_memory(pUniqueIDs[uniqueIDCount].alsa, hwid, sizeof(hwid)); + uniqueIDCount += 1; + } } } else { - if ((type == mal_device_type_playback && (IOID == NULL || strcmp(IOID, "Output") == 0)) || - (type == mal_device_type_capture && (IOID != NULL && strcmp(IOID, "Input" ) == 0))) { - includeThisDevice = MAL_TRUE; + mal_zero_memory(hwid, sizeof(hwid)); + } + + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.id.alsa, sizeof(deviceInfo.id.alsa), hwid, (size_t)-1); + + // DESC is the friendly name. We treat this slightly differently depending on whether or not we are using verbose + // device enumeration. In verbose mode we want to take the entire description so that the end-user can distinguish + // between the subdevices of each card/dev pair. In simplified mode, however, we only want the first part of the + // description. + // + // The value in DESC seems to be split into two lines, with the first line being the name of the device and the + // second line being a description of the device. I don't like having the description be across two lines because + // it makes formatting ugly and annoying. I'm therefore deciding to put it all on a single line with the second line + // being put into parentheses. In simplified mode I'm just stripping the second line entirely. + if (DESC != NULL) { + int lfPos; + const char* line2 = mal_find_char(DESC, '\n', &lfPos); + if (line2 != NULL) { + line2 += 1; // Skip past the new-line character. + + if (pContext->config.alsa.useVerboseDeviceEnumeration) { + // Verbose mode. Put the second line in brackets. + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), DESC, lfPos); + mal_strcat_s (deviceInfo.name, sizeof(deviceInfo.name), " ("); + mal_strcat_s (deviceInfo.name, sizeof(deviceInfo.name), line2); + mal_strcat_s (deviceInfo.name, sizeof(deviceInfo.name), ")"); + } else { + // Simplified mode. Strip the second line entirely. + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), DESC, lfPos); + } + } else { + // There's no second line. Just copy the whole description. + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), DESC, (size_t)-1); } } + if (!mal_is_device_blacklisted__alsa(deviceType, NAME)) { + cbResult = callback(pContext, deviceType, &deviceInfo, pUserData); + } - - if (includeThisDevice) { -#if 0 - printf("NAME: %s\n", NAME); - printf("DESC: %s\n", DESC); - printf("IOID: %s\n", IOID); - - char hwid2[256]; - mal_convert_device_name_to_hw_format__alsa(pContext, hwid2, sizeof(hwid2), NAME); - printf("DEVICE ID: %s (%d)\n\n", hwid2, *pCount); -#endif - - char hwid[sizeof(pUniqueIDs->alsa)]; - if (NAME != NULL) { - if (pContext->config.alsa.useVerboseDeviceEnumeration) { - // Verbose mode. Use the name exactly as-is. - mal_strncpy_s(hwid, sizeof(hwid), NAME, (size_t)-1); + // Some devices are both playback and capture, but they are only enumerated by ALSA once. We need to fire the callback + // again for the other device type in this case. We do this for known devices. + if (cbResult) { + if (mal_is_common_device_name__alsa(NAME)) { + if (deviceType == mal_device_type_playback) { + if (!mal_is_capture_device_blacklisted__alsa(NAME)) { + cbResult = callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); + } } else { - // Simplified mode. Use ":%d,%d" format. - if (mal_convert_device_name_to_hw_format__alsa(pContext, hwid, sizeof(hwid), NAME) == 0) { - // At this point, hwid looks like "hw:0,0". In simplified enumeration mode, we actually want to strip off the - // plugin name so it looks like ":0,0". The reason for this is that this special format is detected at device - // initialization time and is used as an indicator to try and use the most appropriate plugin depending on the - // device type and sharing mode. - char* dst = hwid; - char* src = hwid+2; - while ((*dst++ = *src++)); - } else { - // Conversion to "hw:%d,%d" failed. Just use the name as-is. - mal_strncpy_s(hwid, sizeof(hwid), NAME, (size_t)-1); - } - - if (mal_does_id_exist_in_list__alsa(pUniqueIDs, uniqueIDCount, hwid)) { - goto next_device; // The device has already been enumerated. Move on to the next one. - } else { - // The device has not yet been enumerated. Make sure it's added to our list so that it's not enumerated again. - mal_device_id* pNewUniqueIDs = mal_realloc(pUniqueIDs, sizeof(*pUniqueIDs) * (uniqueIDCount + 1)); - if (pNewUniqueIDs == NULL) { - goto next_device; // Failed to allocate memory. - } - - pUniqueIDs = pNewUniqueIDs; - mal_copy_memory(pUniqueIDs[uniqueIDCount].alsa, hwid, sizeof(hwid)); - uniqueIDCount += 1; + if (!mal_is_playback_device_blacklisted__alsa(NAME)) { + cbResult = callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); } } - } else { - mal_zero_memory(hwid, sizeof(hwid)); } + } - if (pInfo != NULL) { - if (infoSize > 0) { - mal_zero_object(pInfo); - mal_strncpy_s(pInfo->id.alsa, sizeof(pInfo->id.alsa), hwid, (size_t)-1); - - // DESC is the friendly name. We treat this slightly differently depending on whether or not we are using verbose - // device enumeration. In verbose mode we want to take the entire description so that the end-user can distinguish - // between the subdevices of each card/dev pair. In simplified mode, however, we only want the first part of the - // description. - // - // The value in DESC seems to be split into two lines, with the first line being the name of the device and the - // second line being a description of the device. I don't like having the description be across two lines because - // it makes formatting ugly and annoying. I'm therefore deciding to put it all on a single line with the second line - // being put into parentheses. In simplified mode I'm just stripping the second line entirely. - if (DESC != NULL) { - int lfPos; - const char* line2 = mal_find_char(DESC, '\n', &lfPos); - if (line2 != NULL) { - line2 += 1; // Skip past the new-line character. - - if (pContext->config.alsa.useVerboseDeviceEnumeration) { - // Verbose mode. Put the second line in brackets. - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), DESC, lfPos); - mal_strcat_s (pInfo->name, sizeof(pInfo->name), " ("); - mal_strcat_s (pInfo->name, sizeof(pInfo->name), line2); - mal_strcat_s (pInfo->name, sizeof(pInfo->name), ")"); - } else { - // Simplified mode. Strip the second line entirely. - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), DESC, lfPos); - } - } else { - // There's no second line. Just copy the whole description. - mal_strcpy_s(pInfo->name, sizeof(pInfo->name), DESC); - } - } - - pInfo += 1; - infoSize -= 1; - *pCount += 1; - } - } else { - *pCount += 1; - } + if (cbResult == MAL_FALSE) { + stopEnumeration = MAL_TRUE; } next_device: @@ -6250,20 +8182,525 @@ static mal_result mal_enumerate_devices__alsa(mal_context* pContext, mal_device_ free(DESC); free(IOID); ppNextDeviceHint += 1; + + // We need to stop enumeration if the callback returned false. + if (stopEnumeration) { + break; + } } mal_free(pUniqueIDs); - ((mal_snd_device_name_free_hint_proc)pContext->alsa.snd_device_name_free_hint)((void**)ppDeviceHints); + + mal_mutex_unlock(&pContext->alsa.internalDeviceEnumLock); + return MAL_SUCCESS; } -static void mal_device_uninit__alsa(mal_device* pDevice) + +typedef struct +{ + mal_device_type deviceType; + const mal_device_id* pDeviceID; + mal_share_mode shareMode; + mal_device_info* pDeviceInfo; + mal_bool32 foundDevice; +} mal_context_get_device_info_enum_callback_data__alsa; + +mal_bool32 mal_context_get_device_info_enum_callback__alsa(mal_context* pContext, mal_device_type deviceType, const mal_device_info* pDeviceInfo, void* pUserData) +{ + mal_context_get_device_info_enum_callback_data__alsa* pData = (mal_context_get_device_info_enum_callback_data__alsa*)pUserData; + mal_assert(pData != NULL); + + if (pData->pDeviceID == NULL && mal_strcmp(pDeviceInfo->id.alsa, "default") == 0) { + mal_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pDeviceInfo->name, (size_t)-1); + pData->foundDevice = MAL_TRUE; + } else { + if (pData->deviceType == deviceType && mal_context_is_device_id_equal__alsa(pContext, pData->pDeviceID, &pDeviceInfo->id)) { + mal_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pDeviceInfo->name, (size_t)-1); + pData->foundDevice = MAL_TRUE; + } + } + + // Keep enumerating until we have found the device. + return !pData->foundDevice; +} + +mal_result mal_context_get_device_info__alsa(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +{ + mal_assert(pContext != NULL); + + // We just enumerate to find basic information about the device. + mal_context_get_device_info_enum_callback_data__alsa data; + data.deviceType = deviceType; + data.pDeviceID = pDeviceID; + data.shareMode = shareMode; + data.pDeviceInfo = pDeviceInfo; + data.foundDevice = MAL_FALSE; + mal_result result = mal_context_enumerate_devices__alsa(pContext, mal_context_get_device_info_enum_callback__alsa, &data); + if (result != MAL_SUCCESS) { + return result; + } + + if (data.foundDevice) { + return MAL_SUCCESS; + } else { + return MAL_NO_DEVICE; + } +} + +mal_result mal_context_init__alsa(mal_context* pContext) +{ + mal_assert(pContext != NULL); + +#ifndef MAL_NO_RUNTIME_LINKING + pContext->alsa.asoundSO = mal_dlopen("libasound.so"); + if (pContext->alsa.asoundSO == NULL) { + return MAL_NO_BACKEND; + } + + pContext->alsa.snd_pcm_open = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_open"); + pContext->alsa.snd_pcm_close = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_close"); + pContext->alsa.snd_pcm_hw_params_sizeof = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_sizeof"); + pContext->alsa.snd_pcm_hw_params_any = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_any"); + pContext->alsa.snd_pcm_hw_params_set_format = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_format"); + pContext->alsa.snd_pcm_hw_params_set_format_first = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_format_first"); + pContext->alsa.snd_pcm_hw_params_get_format_mask = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_format_mask"); + pContext->alsa.snd_pcm_hw_params_set_channels_near = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_channels_near"); + pContext->alsa.snd_pcm_hw_params_set_rate_resample = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_rate_resample"); + pContext->alsa.snd_pcm_hw_params_set_rate_near = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_rate_near"); + pContext->alsa.snd_pcm_hw_params_set_buffer_size_near = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_buffer_size_near"); + pContext->alsa.snd_pcm_hw_params_set_periods_near = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_periods_near"); + pContext->alsa.snd_pcm_hw_params_set_access = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_access"); + pContext->alsa.snd_pcm_hw_params_get_format = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_format"); + pContext->alsa.snd_pcm_hw_params_get_channels = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_channels"); + pContext->alsa.snd_pcm_hw_params_get_rate = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_rate"); + pContext->alsa.snd_pcm_hw_params_get_buffer_size = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_buffer_size"); + pContext->alsa.snd_pcm_hw_params_get_periods = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_periods"); + pContext->alsa.snd_pcm_hw_params_get_access = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_access"); + pContext->alsa.snd_pcm_hw_params = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params"); + pContext->alsa.snd_pcm_sw_params_sizeof = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params_sizeof"); + pContext->alsa.snd_pcm_sw_params_current = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params_current"); + pContext->alsa.snd_pcm_sw_params_set_avail_min = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params_set_avail_min"); + pContext->alsa.snd_pcm_sw_params_set_start_threshold = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params_set_start_threshold"); + pContext->alsa.snd_pcm_sw_params = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params"); + pContext->alsa.snd_pcm_format_mask_sizeof = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_format_mask_sizeof"); + pContext->alsa.snd_pcm_format_mask_test = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_format_mask_test"); + pContext->alsa.snd_pcm_get_chmap = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_get_chmap"); + pContext->alsa.snd_pcm_prepare = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_prepare"); + pContext->alsa.snd_pcm_start = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_start"); + pContext->alsa.snd_pcm_drop = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_drop"); + pContext->alsa.snd_device_name_hint = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_device_name_hint"); + pContext->alsa.snd_device_name_get_hint = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_device_name_get_hint"); + pContext->alsa.snd_card_get_index = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_card_get_index"); + pContext->alsa.snd_device_name_free_hint = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_device_name_free_hint"); + pContext->alsa.snd_pcm_mmap_begin = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_mmap_begin"); + pContext->alsa.snd_pcm_mmap_commit = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_mmap_commit"); + pContext->alsa.snd_pcm_recover = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_recover"); + pContext->alsa.snd_pcm_readi = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_readi"); + pContext->alsa.snd_pcm_writei = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_writei"); + pContext->alsa.snd_pcm_avail = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_avail"); + pContext->alsa.snd_pcm_avail_update = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_avail_update"); + pContext->alsa.snd_pcm_wait = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_wait"); + pContext->alsa.snd_pcm_info = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_info"); + pContext->alsa.snd_pcm_info_sizeof = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_info_sizeof"); + pContext->alsa.snd_pcm_info_get_name = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_info_get_name"); + pContext->alsa.snd_config_update_free_global = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_config_update_free_global"); +#else + // The system below is just for type safety. + mal_snd_pcm_open_proc _snd_pcm_open = snd_pcm_open; + mal_snd_pcm_close_proc _snd_pcm_close = snd_pcm_close; + mal_snd_pcm_hw_params_sizeof_proc _snd_pcm_hw_params_sizeof = snd_pcm_hw_params_sizeof; + mal_snd_pcm_hw_params_any_proc _snd_pcm_hw_params_any = snd_pcm_hw_params_any; + mal_snd_pcm_hw_params_set_format_proc _snd_pcm_hw_params_set_format = snd_pcm_hw_params_set_format; + mal_snd_pcm_hw_params_set_format_first_proc _snd_pcm_hw_params_set_format_first = snd_pcm_hw_params_set_format_first; + mal_snd_pcm_hw_params_get_format_mask_proc _snd_pcm_hw_params_get_format_mask = snd_pcm_hw_params_get_format_mask; + mal_snd_pcm_hw_params_set_channels_near_proc _snd_pcm_hw_params_set_channels_near = snd_pcm_hw_params_set_channels_near; + mal_snd_pcm_hw_params_set_rate_resample_proc _snd_pcm_hw_params_set_rate_resample = snd_pcm_hw_params_set_rate_resample; + mal_snd_pcm_hw_params_set_rate_near_proc _snd_pcm_hw_params_set_rate_near = snd_pcm_hw_params_set_rate_near; + mal_snd_pcm_hw_params_set_buffer_size_near_proc _snd_pcm_hw_params_set_buffer_size_near = snd_pcm_hw_params_set_buffer_size_near; + mal_snd_pcm_hw_params_set_periods_near_proc _snd_pcm_hw_params_set_periods_near = snd_pcm_hw_params_set_periods_near; + mal_snd_pcm_hw_params_set_access_proc _snd_pcm_hw_params_set_access = snd_pcm_hw_params_set_access; + mal_snd_pcm_hw_params_get_format_proc _snd_pcm_hw_params_get_format = snd_pcm_hw_params_get_format; + mal_snd_pcm_hw_params_get_channels_proc _snd_pcm_hw_params_get_channels = snd_pcm_hw_params_get_channels; + mal_snd_pcm_hw_params_get_rate_proc _snd_pcm_hw_params_get_rate = snd_pcm_hw_params_get_rate; + mal_snd_pcm_hw_params_get_buffer_size_proc _snd_pcm_hw_params_get_buffer_size = snd_pcm_hw_params_get_buffer_size; + mal_snd_pcm_hw_params_get_periods_proc _snd_pcm_hw_params_get_periods = snd_pcm_hw_params_get_periods; + mal_snd_pcm_hw_params_get_access_proc _snd_pcm_hw_params_get_access = snd_pcm_hw_params_get_access; + mal_snd_pcm_hw_params_proc _snd_pcm_hw_params = snd_pcm_hw_params; + mal_snd_pcm_sw_params_sizeof_proc _snd_pcm_sw_params_sizeof = snd_pcm_sw_params_sizeof; + mal_snd_pcm_sw_params_current_proc _snd_pcm_sw_params_current = snd_pcm_sw_params_current; + mal_snd_pcm_sw_params_set_avail_min_proc _snd_pcm_sw_params_set_avail_min = snd_pcm_sw_params_set_avail_min; + mal_snd_pcm_sw_params_set_start_threshold_proc _snd_pcm_sw_params_set_start_threshold = snd_pcm_sw_params_set_start_threshold; + mal_snd_pcm_sw_params_proc _snd_pcm_sw_params = snd_pcm_sw_params; + mal_snd_pcm_format_mask_sizeof_proc _snd_pcm_format_mask_sizeof = snd_pcm_format_mask_sizeof; + mal_snd_pcm_format_mask_test_proc _snd_pcm_format_mask_test = snd_pcm_format_mask_test; + mal_snd_pcm_get_chmap_proc _snd_pcm_get_chmap = snd_pcm_get_chmap; + mal_snd_pcm_prepare_proc _snd_pcm_prepare = snd_pcm_prepare; + mal_snd_pcm_start_proc _snd_pcm_start = snd_pcm_start; + mal_snd_pcm_drop_proc _snd_pcm_drop = snd_pcm_drop; + mal_snd_device_name_hint_proc _snd_device_name_hint = snd_device_name_hint; + mal_snd_device_name_get_hint_proc _snd_device_name_get_hint = snd_device_name_get_hint; + mal_snd_card_get_index_proc _snd_card_get_index = snd_card_get_index; + mal_snd_device_name_free_hint_proc _snd_device_name_free_hint = snd_device_name_free_hint; + mal_snd_pcm_mmap_begin_proc _snd_pcm_mmap_begin = snd_pcm_mmap_begin; + mal_snd_pcm_mmap_commit_proc _snd_pcm_mmap_commit = snd_pcm_mmap_commit; + mal_snd_pcm_recover_proc _snd_pcm_recover = snd_pcm_recover; + mal_snd_pcm_readi_proc _snd_pcm_readi = snd_pcm_readi; + mal_snd_pcm_writei_proc _snd_pcm_writei = snd_pcm_writei; + mal_snd_pcm_avail_proc _snd_pcm_avail = snd_pcm_avail; + mal_snd_pcm_avail_update_proc _snd_pcm_avail_update = snd_pcm_avail_update; + mal_snd_pcm_wait_proc _snd_pcm_wait = snd_pcm_wait; + mal_snd_pcm_info_proc _snd_pcm_info = snd_pcm_info; + mal_snd_pcm_info_sizeof_proc _snd_pcm_info_sizeof = snd_pcm_info_sizeof; + mal_snd_pcm_info_get_name_proc _snd_pcm_info_get_name = snd_pcm_info_get_name; + mal_snd_config_update_free_global_proc _snd_config_update_free_global = snd_config_update_free_global; + + pContext->alsa.snd_pcm_open = (mal_proc)_snd_pcm_open; + pContext->alsa.snd_pcm_close = (mal_proc)_snd_pcm_close; + pContext->alsa.snd_pcm_hw_params_sizeof = (mal_proc)_snd_pcm_hw_params_sizeof; + pContext->alsa.snd_pcm_hw_params_any = (mal_proc)_snd_pcm_hw_params_any; + pContext->alsa.snd_pcm_hw_params_set_format = (mal_proc)_snd_pcm_hw_params_set_format; + pContext->alsa.snd_pcm_hw_params_set_format_first = (mal_proc)_snd_pcm_hw_params_set_format_first; + pContext->alsa.snd_pcm_hw_params_get_format_mask = (mal_proc)_snd_pcm_hw_params_get_format_mask; + pContext->alsa.snd_pcm_hw_params_set_channels_near = (mal_proc)_snd_pcm_hw_params_set_channels_near; + pContext->alsa.snd_pcm_hw_params_set_rate_resample = (mal_proc)_snd_pcm_hw_params_set_rate_resample; + pContext->alsa.snd_pcm_hw_params_set_rate_near = (mal_proc)_snd_pcm_hw_params_set_rate_near; + pContext->alsa.snd_pcm_hw_params_set_buffer_size_near = (mal_proc)_snd_pcm_hw_params_set_buffer_size_near; + pContext->alsa.snd_pcm_hw_params_set_periods_near = (mal_proc)_snd_pcm_hw_params_set_periods_near; + pContext->alsa.snd_pcm_hw_params_set_access = (mal_proc)_snd_pcm_hw_params_set_access; + pContext->alsa.snd_pcm_hw_params_get_format = (mal_proc)_snd_pcm_hw_params_get_format; + pContext->alsa.snd_pcm_hw_params_get_channels = (mal_proc)_snd_pcm_hw_params_get_channels; + pContext->alsa.snd_pcm_hw_params_get_rate = (mal_proc)_snd_pcm_hw_params_get_rate; + pContext->alsa.snd_pcm_hw_params_get_buffer_size = (mal_proc)_snd_pcm_hw_params_get_buffer_size; + pContext->alsa.snd_pcm_hw_params_get_periods = (mal_proc)_snd_pcm_hw_params_get_periods; + pContext->alsa.snd_pcm_hw_params_get_access = (mal_proc)_snd_pcm_hw_params_get_access; + pContext->alsa.snd_pcm_hw_params = (mal_proc)_snd_pcm_hw_params; + pContext->alsa.snd_pcm_sw_params_sizeof = (mal_proc)_snd_pcm_sw_params_sizeof; + pContext->alsa.snd_pcm_sw_params_current = (mal_proc)_snd_pcm_sw_params_current; + pContext->alsa.snd_pcm_sw_params_set_avail_min = (mal_proc)_snd_pcm_sw_params_set_avail_min; + pContext->alsa.snd_pcm_sw_params_set_start_threshold = (mal_proc)_snd_pcm_sw_params_set_start_threshold; + pContext->alsa.snd_pcm_sw_params = (mal_proc)_snd_pcm_sw_params; + pContext->alsa.snd_pcm_format_mask_sizeof = (mal_proc)_snd_pcm_format_mask_sizeof; + pContext->alsa.snd_pcm_format_mask_test = (mal_proc)_snd_pcm_format_mask_test; + pContext->alsa.snd_pcm_get_chmap = (mal_proc)_snd_pcm_get_chmap; + pContext->alsa.snd_pcm_prepare = (mal_proc)_snd_pcm_prepare; + pContext->alsa.snd_pcm_start = (mal_proc)_snd_pcm_start; + pContext->alsa.snd_pcm_drop = (mal_proc)_snd_pcm_drop; + pContext->alsa.snd_device_name_hint = (mal_proc)_snd_device_name_hint; + pContext->alsa.snd_device_name_get_hint = (mal_proc)_snd_device_name_get_hint; + pContext->alsa.snd_card_get_index = (mal_proc)_snd_card_get_index; + pContext->alsa.snd_device_name_free_hint = (mal_proc)_snd_device_name_free_hint; + pContext->alsa.snd_pcm_mmap_begin = (mal_proc)_snd_pcm_mmap_begin; + pContext->alsa.snd_pcm_mmap_commit = (mal_proc)_snd_pcm_mmap_commit; + pContext->alsa.snd_pcm_recover = (mal_proc)_snd_pcm_recover; + pContext->alsa.snd_pcm_readi = (mal_proc)_snd_pcm_readi; + pContext->alsa.snd_pcm_writei = (mal_proc)_snd_pcm_writei; + pContext->alsa.snd_pcm_avail = (mal_proc)_snd_pcm_avail; + pContext->alsa.snd_pcm_avail_update = (mal_proc)_snd_pcm_avail_update; + pContext->alsa.snd_pcm_wait = (mal_proc)_snd_pcm_wait; + pContext->alsa.snd_pcm_info = (mal_proc)_snd_pcm_info; + pContext->alsa.snd_pcm_info_sizeof = (mal_proc)_snd_pcm_info_sizeof; + pContext->alsa.snd_pcm_info_get_name = (mal_proc)_snd_pcm_info_get_name; + pContext->alsa.snd_config_update_free_global = (mal_proc)_snd_config_update_free_global; +#endif + + if (mal_mutex_init(pContext, &pContext->alsa.internalDeviceEnumLock) != MAL_SUCCESS) { + mal_context_post_error(pContext, NULL, "[ALSA] WARNING: Failed to initialize mutex for internal device enumeration.", MAL_ERROR); + } + + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__alsa; + pContext->onEnumDevices = mal_context_enumerate_devices__alsa; + pContext->onGetDeviceInfo = mal_context_get_device_info__alsa; + + return MAL_SUCCESS; +} + +mal_result mal_context_uninit__alsa(mal_context* pContext) +{ + mal_assert(pContext != NULL); + mal_assert(pContext->backend == mal_backend_alsa); + + // Clean up memory for memory leak checkers. + ((mal_snd_config_update_free_global_proc)pContext->alsa.snd_config_update_free_global)(); + +#ifndef MAL_NO_RUNTIME_LINKING + mal_dlclose(pContext->alsa.asoundSO); +#endif + + mal_mutex_uninit(&pContext->alsa.internalDeviceEnumLock); + + return MAL_SUCCESS; +} + + +// Waits for a number of frames to become available for either capture or playback. The return +// value is the number of frames available. +// +// This will return early if the main loop is broken with mal_device__break_main_loop(). +mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice, mal_bool32* pRequiresRestart) { mal_assert(pDevice != NULL); - if ((snd_pcm_t*)pDevice->alsa.pPCM) { - ((mal_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)((snd_pcm_t*)pDevice->alsa.pPCM); + if (pRequiresRestart) *pRequiresRestart = MAL_FALSE; + + while (!pDevice->alsa.breakFromMainLoop) { + int waitResult = ((mal_snd_pcm_wait_proc)pDevice->pContext->alsa.snd_pcm_wait)((mal_snd_pcm_t*)pDevice->alsa.pPCM, -1); + if (waitResult < 0) { + if (waitResult == -EPIPE) { + if (((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((mal_snd_pcm_t*)pDevice->alsa.pPCM, waitResult, MAL_TRUE) < 0) { + return 0; + } + + if (pRequiresRestart) *pRequiresRestart = MAL_TRUE; // A device recovery means a restart for mmap mode. + } + } + + if (pDevice->alsa.breakFromMainLoop) { + return 0; + } + + mal_snd_pcm_sframes_t framesAvailable = ((mal_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((mal_snd_pcm_t*)pDevice->alsa.pPCM); + if (framesAvailable < 0) { + if (framesAvailable == -EPIPE) { + if (((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((mal_snd_pcm_t*)pDevice->alsa.pPCM, framesAvailable, MAL_TRUE) < 0) { + return 0; + } + + if (pRequiresRestart) *pRequiresRestart = MAL_TRUE; // A device recovery means a restart for mmap mode. + + // Try again, but if it fails this time just return an error. + framesAvailable = ((mal_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((mal_snd_pcm_t*)pDevice->alsa.pPCM); + if (framesAvailable < 0) { + return 0; + } + } + } + + // Ideally I'd like to keep the number of frames consistent with the period size, but unfortunately it appears + // this does not work correctly in some situations. In my testing, this breaks when the period size is <= 1024 + // when using "hw:0,0" in a VirtualBox guest. What's happening is that it looks like snd_pcm_writei() (and + // snd_pcm_mmap_commit() in MMAP mode) are not physically writing the data to the internal buffers. As a result, + // snd_pcm_wait() is returning immediately, always reporting the full buffer size as available. I'm not sure if + // this is me not doing something right, or if it's some kind of driver bug, but to fix this we just need to + // report the exact value returned by snd_pcm_avail_update() and not clamp it to the period size. +#if 1 + return framesAvailable; +#else + mal_uint32 periodSizeInFrames = pDevice->bufferSizeInFrames / pDevice->periods; + if (framesAvailable >= periodSizeInFrames) { + return periodSizeInFrames; + } +#endif + } + + // We'll get here if the loop was terminated. Just return whatever's available. + mal_snd_pcm_sframes_t framesAvailable = ((mal_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((mal_snd_pcm_t*)pDevice->alsa.pPCM); + if (framesAvailable < 0) { + return 0; + } + + return framesAvailable; +} + +mal_bool32 mal_device_write__alsa(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + if (!mal_device_is_started(pDevice) && mal_device__get_state(pDevice) != MAL_STATE_STARTING) { + return MAL_FALSE; + } + if (pDevice->alsa.breakFromMainLoop) { + return MAL_FALSE; + } + + + if (pDevice->alsa.isUsingMMap) { + // mmap. + mal_bool32 requiresRestart; + mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, &requiresRestart); + if (framesAvailable == 0) { + return MAL_FALSE; + } + + // Don't bother asking the client for more audio data if we're just stopping the device anyway. + if (pDevice->alsa.breakFromMainLoop) { + return MAL_FALSE; + } + + const mal_snd_pcm_channel_area_t* pAreas; + mal_snd_pcm_uframes_t mappedOffset; + mal_snd_pcm_uframes_t mappedFrames = framesAvailable; + while (framesAvailable > 0) { + int result = ((mal_snd_pcm_mmap_begin_proc)pDevice->pContext->alsa.snd_pcm_mmap_begin)((mal_snd_pcm_t*)pDevice->alsa.pPCM, &pAreas, &mappedOffset, &mappedFrames); + if (result < 0) { + return MAL_FALSE; + } + + if (mappedFrames > 0) { + void* pBuffer = (mal_uint8*)pAreas[0].addr + ((pAreas[0].first + (mappedOffset * pAreas[0].step)) / 8); + mal_device__read_frames_from_client(pDevice, mappedFrames, pBuffer); + } + + result = ((mal_snd_pcm_mmap_commit_proc)pDevice->pContext->alsa.snd_pcm_mmap_commit)((mal_snd_pcm_t*)pDevice->alsa.pPCM, mappedOffset, mappedFrames); + if (result < 0 || (mal_snd_pcm_uframes_t)result != mappedFrames) { + ((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((mal_snd_pcm_t*)pDevice->alsa.pPCM, result, MAL_TRUE); + return MAL_FALSE; + } + + if (requiresRestart) { + if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((mal_snd_pcm_t*)pDevice->alsa.pPCM) < 0) { + return MAL_FALSE; + } + } + + framesAvailable -= mappedFrames; + } + } else { + // readi/writei. + while (!pDevice->alsa.breakFromMainLoop) { + mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, NULL); + if (framesAvailable == 0) { + continue; + } + + // Don't bother asking the client for more audio data if we're just stopping the device anyway. + if (pDevice->alsa.breakFromMainLoop) { + return MAL_FALSE; + } + + mal_device__read_frames_from_client(pDevice, framesAvailable, pDevice->alsa.pIntermediaryBuffer); + + mal_snd_pcm_sframes_t framesWritten = ((mal_snd_pcm_writei_proc)pDevice->pContext->alsa.snd_pcm_writei)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); + if (framesWritten < 0) { + if (framesWritten == -EAGAIN) { + continue; // Just keep trying... + } else if (framesWritten == -EPIPE) { + // Underrun. Just recover and try writing again. + if (((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((mal_snd_pcm_t*)pDevice->alsa.pPCM, framesWritten, MAL_TRUE) < 0) { + mal_post_error(pDevice, "[ALSA] Failed to recover device after underrun.", MAL_FAILED_TO_START_BACKEND_DEVICE); + return MAL_FALSE; + } + + framesWritten = ((mal_snd_pcm_writei_proc)pDevice->pContext->alsa.snd_pcm_writei)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); + if (framesWritten < 0) { + mal_post_error(pDevice, "[ALSA] Failed to write data to the internal device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); + return MAL_FALSE; + } + + break; // Success. + } else { + mal_post_error(pDevice, "[ALSA] snd_pcm_writei() failed when writing initial data.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); + return MAL_FALSE; + } + } else { + break; // Success. + } + } + } + + return MAL_TRUE; +} + +mal_bool32 mal_device_read__alsa(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + if (!mal_device_is_started(pDevice)) { + return MAL_FALSE; + } + if (pDevice->alsa.breakFromMainLoop) { + return MAL_FALSE; + } + + mal_uint32 framesToSend = 0; + void* pBuffer = NULL; + if (pDevice->alsa.pIntermediaryBuffer == NULL) { + // mmap. + mal_bool32 requiresRestart; + mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, &requiresRestart); + if (framesAvailable == 0) { + return MAL_FALSE; + } + + const mal_snd_pcm_channel_area_t* pAreas; + mal_snd_pcm_uframes_t mappedOffset; + mal_snd_pcm_uframes_t mappedFrames = framesAvailable; + while (framesAvailable > 0) { + int result = ((mal_snd_pcm_mmap_begin_proc)pDevice->pContext->alsa.snd_pcm_mmap_begin)((mal_snd_pcm_t*)pDevice->alsa.pPCM, &pAreas, &mappedOffset, &mappedFrames); + if (result < 0) { + return MAL_FALSE; + } + + if (mappedFrames > 0) { + void* pBuffer = (mal_uint8*)pAreas[0].addr + ((pAreas[0].first + (mappedOffset * pAreas[0].step)) / 8); + mal_device__send_frames_to_client(pDevice, mappedFrames, pBuffer); + } + + result = ((mal_snd_pcm_mmap_commit_proc)pDevice->pContext->alsa.snd_pcm_mmap_commit)((mal_snd_pcm_t*)pDevice->alsa.pPCM, mappedOffset, mappedFrames); + if (result < 0 || (mal_snd_pcm_uframes_t)result != mappedFrames) { + ((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((mal_snd_pcm_t*)pDevice->alsa.pPCM, result, MAL_TRUE); + return MAL_FALSE; + } + + if (requiresRestart) { + if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((mal_snd_pcm_t*)pDevice->alsa.pPCM) < 0) { + return MAL_FALSE; + } + } + + framesAvailable -= mappedFrames; + } + } else { + // readi/writei. + mal_snd_pcm_sframes_t framesRead = 0; + while (!pDevice->alsa.breakFromMainLoop) { + mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, NULL); + if (framesAvailable == 0) { + continue; + } + + framesRead = ((mal_snd_pcm_readi_proc)pDevice->pContext->alsa.snd_pcm_readi)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); + if (framesRead < 0) { + if (framesRead == -EAGAIN) { + continue; // Just keep trying... + } else if (framesRead == -EPIPE) { + // Overrun. Just recover and try reading again. + if (((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((mal_snd_pcm_t*)pDevice->alsa.pPCM, framesRead, MAL_TRUE) < 0) { + mal_post_error(pDevice, "[ALSA] Failed to recover device after overrun.", MAL_FAILED_TO_START_BACKEND_DEVICE); + return MAL_FALSE; + } + + framesRead = ((mal_snd_pcm_readi_proc)pDevice->pContext->alsa.snd_pcm_readi)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); + if (framesRead < 0) { + mal_post_error(pDevice, "[ALSA] Failed to read data from the internal device.", MAL_FAILED_TO_READ_DATA_FROM_DEVICE); + return MAL_FALSE; + } + + break; // Success. + } else { + return MAL_FALSE; + } + } else { + break; // Success. + } + } + + framesToSend = framesRead; + pBuffer = pDevice->alsa.pIntermediaryBuffer; + } + + if (framesToSend > 0) { + mal_device__send_frames_to_client(pDevice, framesToSend, pBuffer); + } + + return MAL_TRUE; +} + +void mal_device_uninit__alsa(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + if ((mal_snd_pcm_t*)pDevice->alsa.pPCM) { + ((mal_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)((mal_snd_pcm_t*)pDevice->alsa.pPCM); if (pDevice->alsa.pIntermediaryBuffer != NULL) { mal_free(pDevice->alsa.pIntermediaryBuffer); @@ -6271,15 +8708,17 @@ static void mal_device_uninit__alsa(mal_device* pDevice) } } -static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) { (void)pContext; mal_assert(pDevice != NULL); mal_zero_object(&pDevice->alsa); - snd_pcm_format_t formatALSA = mal_convert_mal_format_to_alsa_format(pConfig->format); - snd_pcm_stream_t stream = (type == mal_device_type_playback) ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE; + mal_snd_pcm_format_t formatALSA = mal_convert_mal_format_to_alsa_format(pConfig->format); + mal_snd_pcm_stream_t stream = (type == mal_device_type_playback) ? MAL_SND_PCM_STREAM_PLAYBACK : MAL_SND_PCM_STREAM_CAPTURE; + + int openMode = MAL_SND_PCM_NO_AUTO_RESAMPLE | MAL_SND_PCM_NO_AUTO_CHANNELS | MAL_SND_PCM_NO_AUTO_FORMAT; if (pDeviceID == NULL) { // We're opening the default device. I don't know if trying anything other than "default" is necessary, but it makes @@ -6294,7 +8733,7 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t NULL }; - if (pConfig->preferExclusiveMode) { + if (pConfig->shareMode == mal_share_mode_exclusive) { defaultDeviceNames[1] = "hw"; defaultDeviceNames[2] = "hw:0"; defaultDeviceNames[3] = "hw:0,0"; @@ -6316,7 +8755,7 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t mal_bool32 isDeviceOpen = MAL_FALSE; for (size_t i = 0; i < mal_countof(defaultDeviceNames); ++i) { if (defaultDeviceNames[i] != NULL && defaultDeviceNames[i][0] != '\0') { - if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((snd_pcm_t**)&pDevice->alsa.pPCM, defaultDeviceNames[i], stream, 0) == 0) { + if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((mal_snd_pcm_t**)&pDevice->alsa.pPCM, defaultDeviceNames[i], stream, openMode) == 0) { isDeviceOpen = MAL_TRUE; break; } @@ -6325,7 +8764,7 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t if (!isDeviceOpen) { mal_device_uninit__alsa(pDevice); - return mal_post_error(pDevice, "[ALSA] snd_pcm_open() failed when trying to open an appropriate default device.", MAL_ALSA_FAILED_TO_OPEN_DEVICE); + return mal_post_error(pDevice, "[ALSA] snd_pcm_open() failed when trying to open an appropriate default device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } } else { // We're trying to open a specific device. There's a few things to consider here: @@ -6336,7 +8775,7 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t mal_bool32 isDeviceOpen = MAL_FALSE; if (pDeviceID->alsa[0] != ':') { // The ID is not in ":0,0" format. Use the ID exactly as-is. - if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((snd_pcm_t**)&pDevice->alsa.pPCM, pDeviceID->alsa, stream, 0) == 0) { + if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((mal_snd_pcm_t**)&pDevice->alsa.pPCM, pDeviceID->alsa, stream, openMode) == 0) { isDeviceOpen = MAL_TRUE; } } else { @@ -6346,7 +8785,7 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t } char hwid[256]; - if (!pConfig->preferExclusiveMode) { + if (pConfig->shareMode == mal_share_mode_shared) { if (type == mal_device_type_playback) { mal_strcpy_s(hwid, sizeof(hwid), "dmix"); } else { @@ -6354,7 +8793,7 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t } if (mal_strcat_s(hwid, sizeof(hwid), pDeviceID->alsa) == 0) { - if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((snd_pcm_t**)&pDevice->alsa.pPCM, hwid, stream, 0) == 0) { + if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((mal_snd_pcm_t**)&pDevice->alsa.pPCM, hwid, stream, openMode) == 0) { isDeviceOpen = MAL_TRUE; } } @@ -6364,7 +8803,7 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t if (!isDeviceOpen) { mal_strcpy_s(hwid, sizeof(hwid), "hw"); if (mal_strcat_s(hwid, sizeof(hwid), pDeviceID->alsa) == 0) { - if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((snd_pcm_t**)&pDevice->alsa.pPCM, hwid, stream, 0) == 0) { + if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((mal_snd_pcm_t**)&pDevice->alsa.pPCM, hwid, stream, openMode) == 0) { isDeviceOpen = MAL_TRUE; } } @@ -6373,21 +8812,21 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t if (!isDeviceOpen) { mal_device_uninit__alsa(pDevice); - return mal_post_error(pDevice, "[ALSA] snd_pcm_open() failed.", MAL_ALSA_FAILED_TO_OPEN_DEVICE); + return mal_post_error(pDevice, "[ALSA] snd_pcm_open() failed.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } } // We may need to scale the size of the buffer depending on the device. if (pDevice->usingDefaultBufferSize) { - float bufferSizeScale = 1; + mal_snd_pcm_info_t* pInfo = (mal_snd_pcm_info_t*)alloca(((mal_snd_pcm_info_sizeof_proc)pContext->alsa.snd_pcm_info_sizeof)()); + mal_zero_memory(pInfo, ((mal_snd_pcm_info_sizeof_proc)pContext->alsa.snd_pcm_info_sizeof)()); - snd_pcm_info_t* pInfo = (snd_pcm_info_t*)alloca(((mal_snd_pcm_info_sizeof)pContext->alsa.snd_pcm_info_sizeof)()); - mal_zero_memory(pInfo, ((mal_snd_pcm_info_sizeof)pContext->alsa.snd_pcm_info_sizeof)()); + if (((mal_snd_pcm_info_proc)pContext->alsa.snd_pcm_info)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pInfo) == 0) { + float bufferSizeScale = 1; - if (((mal_snd_pcm_info)pContext->alsa.snd_pcm_info)((snd_pcm_t*)pDevice->alsa.pPCM, pInfo) == 0) { - const char* deviceName = ((mal_snd_pcm_info_get_name)pContext->alsa.snd_pcm_info_get_name)(pInfo); + const char* deviceName = ((mal_snd_pcm_info_get_name_proc)pContext->alsa.snd_pcm_info_get_name)(pInfo); if (deviceName != NULL) { - if (strcmp(deviceName, "default") == 0) { + if (mal_strcmp(deviceName, "default") == 0) { // It's the default device. We need to use DESC from snd_device_name_hint(). char** ppDeviceHints; if (((mal_snd_device_name_hint_proc)pContext->alsa.snd_device_name_hint)(-1, "pcm", (void***)&ppDeviceHints) < 0) { @@ -6401,9 +8840,9 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t char* IOID = ((mal_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "IOID"); mal_bool32 foundDevice = MAL_FALSE; - if ((type == mal_device_type_playback && (IOID == NULL || strcmp(IOID, "Output") == 0)) || - (type == mal_device_type_capture && (IOID != NULL && strcmp(IOID, "Input" ) == 0))) { - if (strcmp(NAME, deviceName) == 0) { + if ((type == mal_device_type_playback && (IOID == NULL || mal_strcmp(IOID, "Output") == 0)) || + (type == mal_device_type_capture && (IOID != NULL && mal_strcmp(IOID, "Input" ) == 0))) { + if (mal_strcmp(NAME, deviceName) == 0) { bufferSizeScale = mal_find_default_buffer_size_scale__alsa(DESC); foundDevice = MAL_TRUE; } @@ -6431,12 +8870,12 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t // Hardware parameters. - snd_pcm_hw_params_t* pHWParams = (snd_pcm_hw_params_t*)alloca(((mal_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)()); + mal_snd_pcm_hw_params_t* pHWParams = (mal_snd_pcm_hw_params_t*)alloca(((mal_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)()); mal_zero_memory(pHWParams, ((mal_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)()); - if (((mal_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams) < 0) { + if (((mal_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pHWParams) < 0) { mal_device_uninit__alsa(pDevice); - return mal_post_error(pDevice, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.", MAL_ALSA_FAILED_TO_SET_HW_PARAMS); + return mal_post_error(pDevice, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.", MAL_FAILED_TO_CONFIGURE_BACKEND_DEVICE); } @@ -6445,13 +8884,13 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t // Try using interleaved MMAP access. If this fails, fall back to standard readi/writei. pDevice->alsa.isUsingMMap = MAL_FALSE; if (!pConfig->alsa.noMMap && pDevice->type != mal_device_type_capture) { // <-- Disabling MMAP mode for capture devices because I apparently do not have a device that supports it so I can test it... Contributions welcome. - if (((mal_snd_pcm_hw_params_set_access_proc)pContext->alsa.snd_pcm_hw_params_set_access)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, SND_PCM_ACCESS_MMAP_INTERLEAVED) == 0) { + if (((mal_snd_pcm_hw_params_set_access_proc)pContext->alsa.snd_pcm_hw_params_set_access)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, MAL_SND_PCM_ACCESS_MMAP_INTERLEAVED) == 0) { pDevice->alsa.isUsingMMap = MAL_TRUE; } } if (!pDevice->alsa.isUsingMMap) { - if (((mal_snd_pcm_hw_params_set_access_proc)pContext->alsa.snd_pcm_hw_params_set_access)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {; + if (((mal_snd_pcm_hw_params_set_access_proc)pContext->alsa.snd_pcm_hw_params_set_access)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, MAL_SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {; mal_device_uninit__alsa(pDevice); return mal_post_error(pDevice, "[ALSA] Failed to set access mode to neither SND_PCM_ACCESS_MMAP_INTERLEAVED nor SND_PCM_ACCESS_RW_INTERLEAVED. snd_pcm_hw_params_set_access() failed.", MAL_FORMAT_NOT_SUPPORTED); } @@ -6463,7 +8902,7 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t // Format. // Try getting every supported format. - snd_pcm_format_mask_t* pFormatMask = (snd_pcm_format_mask_t*)alloca(((mal_snd_pcm_format_mask_sizeof_proc)pContext->alsa.snd_pcm_format_mask_sizeof)()); + mal_snd_pcm_format_mask_t* pFormatMask = (mal_snd_pcm_format_mask_t*)alloca(((mal_snd_pcm_format_mask_sizeof_proc)pContext->alsa.snd_pcm_format_mask_sizeof)()); mal_zero_memory(pFormatMask, ((mal_snd_pcm_format_mask_sizeof_proc)pContext->alsa.snd_pcm_format_mask_sizeof)()); ((mal_snd_pcm_hw_params_get_format_mask_proc)pContext->alsa.snd_pcm_hw_params_get_format_mask)(pHWParams, pFormatMask); @@ -6472,15 +8911,16 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t // supported, and if so, use that one. If it's not supported, we just run though a list of formats and try to find the best one. if (!((mal_snd_pcm_format_mask_test_proc)pContext->alsa.snd_pcm_format_mask_test)(pFormatMask, formatALSA)) { // The requested format is not supported so now try running through the list of formats and return the best one. - snd_pcm_format_t preferredFormatsALSA[] = { - SND_PCM_FORMAT_FLOAT_LE, // mal_format_f32 - SND_PCM_FORMAT_S32_LE, // mal_format_s32 - SND_PCM_FORMAT_S24_3LE, // mal_format_s24 - SND_PCM_FORMAT_S16_LE, // mal_format_s16 - SND_PCM_FORMAT_U8 // mal_format_u8 + mal_snd_pcm_format_t preferredFormatsALSA[] = { + MAL_SND_PCM_FORMAT_FLOAT_LE, // mal_format_f32 + MAL_SND_PCM_FORMAT_S32_LE, // mal_format_s32 + MAL_SND_PCM_FORMAT_S24_3LE, // mal_format_s24 + //MAL_SND_PCM_FORMAT_S24_LE, // mal_format_s24_32 + MAL_SND_PCM_FORMAT_S16_LE, // mal_format_s16 + MAL_SND_PCM_FORMAT_U8 // mal_format_u8 }; - formatALSA = SND_PCM_FORMAT_UNKNOWN; + formatALSA = MAL_SND_PCM_FORMAT_UNKNOWN; for (size_t i = 0; i < (sizeof(preferredFormatsALSA) / sizeof(preferredFormatsALSA[0])); ++i) { if (((mal_snd_pcm_format_mask_test_proc)pContext->alsa.snd_pcm_format_mask_test)(pFormatMask, preferredFormatsALSA[i])) { formatALSA = preferredFormatsALSA[i]; @@ -6488,13 +8928,13 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t } } - if (formatALSA == SND_PCM_FORMAT_UNKNOWN) { + if (formatALSA == MAL_SND_PCM_FORMAT_UNKNOWN) { mal_device_uninit__alsa(pDevice); return mal_post_error(pDevice, "[ALSA] Format not supported. The device does not support any mini_al formats.", MAL_FORMAT_NOT_SUPPORTED); } } - if (((mal_snd_pcm_hw_params_set_format_proc)pContext->alsa.snd_pcm_hw_params_set_format)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, formatALSA) < 0) { + if (((mal_snd_pcm_hw_params_set_format_proc)pContext->alsa.snd_pcm_hw_params_set_format)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, formatALSA) < 0) { mal_device_uninit__alsa(pDevice); return mal_post_error(pDevice, "[ALSA] Format not supported. snd_pcm_hw_params_set_format() failed.", MAL_FORMAT_NOT_SUPPORTED); } @@ -6505,13 +8945,14 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t return mal_post_error(pDevice, "[ALSA] The chosen format is not supported by mini_al.", MAL_FORMAT_NOT_SUPPORTED); } + // Channels. - mal_uint32 channels = pConfig->channels; - if (((mal_snd_pcm_hw_params_set_channels_near_proc)pContext->alsa.snd_pcm_hw_params_set_channels_near)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &channels) < 0) { + unsigned int channels = pConfig->channels; + if (((mal_snd_pcm_hw_params_set_channels_near_proc)pContext->alsa.snd_pcm_hw_params_set_channels_near)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &channels) < 0) { mal_device_uninit__alsa(pDevice); return mal_post_error(pDevice, "[ALSA] Failed to set channel count. snd_pcm_hw_params_set_channels_near() failed.", MAL_FORMAT_NOT_SUPPORTED); } - pDevice->internalChannels = channels; + pDevice->internalChannels = (mal_uint32)channels; // Sample Rate. It appears there's either a bug in ALSA, a bug in some drivers, or I'm doing something silly; but having resampling @@ -6529,28 +8970,28 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t // // I don't currently know if the dmix plugin is the only one with this error. Indeed, this is the only one I've been able to reproduce // this error with. In the future, we may want to restrict the disabling of resampling to only known bad plugins. - ((mal_snd_pcm_hw_params_set_rate_resample_proc)pContext->alsa.snd_pcm_hw_params_set_rate_resample)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, 0); + ((mal_snd_pcm_hw_params_set_rate_resample_proc)pContext->alsa.snd_pcm_hw_params_set_rate_resample)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, 0); - mal_uint32 sampleRate = pConfig->sampleRate; - if (((mal_snd_pcm_hw_params_set_rate_near_proc)pContext->alsa.snd_pcm_hw_params_set_rate_near)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &sampleRate, 0) < 0) { + unsigned int sampleRate = pConfig->sampleRate; + if (((mal_snd_pcm_hw_params_set_rate_near_proc)pContext->alsa.snd_pcm_hw_params_set_rate_near)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &sampleRate, 0) < 0) { mal_device_uninit__alsa(pDevice); return mal_post_error(pDevice, "[ALSA] Sample rate not supported. snd_pcm_hw_params_set_rate_near() failed.", MAL_FORMAT_NOT_SUPPORTED); } - pDevice->internalSampleRate = sampleRate; + pDevice->internalSampleRate = (mal_uint32)sampleRate; // Periods. mal_uint32 periods = pConfig->periods; int dir = 0; - if (((mal_snd_pcm_hw_params_set_periods_near_proc)pContext->alsa.snd_pcm_hw_params_set_periods_near)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &periods, &dir) < 0) { + if (((mal_snd_pcm_hw_params_set_periods_near_proc)pContext->alsa.snd_pcm_hw_params_set_periods_near)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &periods, &dir) < 0) { mal_device_uninit__alsa(pDevice); return mal_post_error(pDevice, "[ALSA] Failed to set period count. snd_pcm_hw_params_set_periods_near() failed.", MAL_FORMAT_NOT_SUPPORTED); } pDevice->periods = periods; // Buffer Size - snd_pcm_uframes_t actualBufferSize = pDevice->bufferSizeInFrames; - if (((mal_snd_pcm_hw_params_set_buffer_size_near_proc)pContext->alsa.snd_pcm_hw_params_set_buffer_size_near)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &actualBufferSize) < 0) { + mal_snd_pcm_uframes_t actualBufferSize = pDevice->bufferSizeInFrames; + if (((mal_snd_pcm_hw_params_set_buffer_size_near_proc)pContext->alsa.snd_pcm_hw_params_set_buffer_size_near)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &actualBufferSize) < 0) { mal_device_uninit__alsa(pDevice); return mal_post_error(pDevice, "[ALSA] Failed to set buffer size for device. snd_pcm_hw_params_set_buffer_size() failed.", MAL_FORMAT_NOT_SUPPORTED); } @@ -6558,56 +8999,53 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t // Apply hardware parameters. - if (((mal_snd_pcm_hw_params_proc)pContext->alsa.snd_pcm_hw_params)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams) < 0) { + if (((mal_snd_pcm_hw_params_proc)pContext->alsa.snd_pcm_hw_params)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pHWParams) < 0) { mal_device_uninit__alsa(pDevice); - return mal_post_error(pDevice, "[ALSA] Failed to set hardware parameters. snd_pcm_hw_params() failed.", MAL_ALSA_FAILED_TO_SET_HW_PARAMS); + return mal_post_error(pDevice, "[ALSA] Failed to set hardware parameters. snd_pcm_hw_params() failed.", MAL_FAILED_TO_CONFIGURE_BACKEND_DEVICE); } - // Software parameters. - snd_pcm_sw_params_t* pSWParams = (snd_pcm_sw_params_t*)alloca(((mal_snd_pcm_sw_params_sizeof_proc)pContext->alsa.snd_pcm_sw_params_sizeof)()); + mal_snd_pcm_sw_params_t* pSWParams = (mal_snd_pcm_sw_params_t*)alloca(((mal_snd_pcm_sw_params_sizeof_proc)pContext->alsa.snd_pcm_sw_params_sizeof)()); mal_zero_memory(pSWParams, ((mal_snd_pcm_sw_params_sizeof_proc)pContext->alsa.snd_pcm_sw_params_sizeof)()); - if (((mal_snd_pcm_sw_params_current_proc)pContext->alsa.snd_pcm_sw_params_current)((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams) != 0) { + if (((mal_snd_pcm_sw_params_current_proc)pContext->alsa.snd_pcm_sw_params_current)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pSWParams) != 0) { mal_device_uninit__alsa(pDevice); - return mal_post_error(pDevice, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS); + return mal_post_error(pDevice, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed.", MAL_FAILED_TO_CONFIGURE_BACKEND_DEVICE); } - if (((mal_snd_pcm_sw_params_set_avail_min_proc)pContext->alsa.snd_pcm_sw_params_set_avail_min)((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, (pDevice->sampleRate/1000) * 1) != 0) { + if (((mal_snd_pcm_sw_params_set_avail_min_proc)pContext->alsa.snd_pcm_sw_params_set_avail_min)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, /*(pDevice->sampleRate/1000) * 1*/ mal_prev_power_of_2(pDevice->bufferSizeInFrames/pDevice->periods)) != 0) { mal_device_uninit__alsa(pDevice); return mal_post_error(pDevice, "[ALSA] snd_pcm_sw_params_set_avail_min() failed.", MAL_FORMAT_NOT_SUPPORTED); } if (type == mal_device_type_playback && !pDevice->alsa.isUsingMMap) { // Only playback devices in writei/readi mode need a start threshold. - if (((mal_snd_pcm_sw_params_set_start_threshold_proc)pContext->alsa.snd_pcm_sw_params_set_start_threshold)((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, (pDevice->sampleRate/1000) * 1) != 0) { //mal_prev_power_of_2(pDevice->bufferSizeInFrames/pDevice->periods) + if (((mal_snd_pcm_sw_params_set_start_threshold_proc)pContext->alsa.snd_pcm_sw_params_set_start_threshold)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, /*(pDevice->sampleRate/1000) * 1*/ pDevice->bufferSizeInFrames/pDevice->periods) != 0) { //mal_prev_power_of_2(pDevice->bufferSizeInFrames/pDevice->periods) mal_device_uninit__alsa(pDevice); - return mal_post_error(pDevice, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS); + return mal_post_error(pDevice, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed.", MAL_FAILED_TO_CONFIGURE_BACKEND_DEVICE); } } - if (((mal_snd_pcm_sw_params_proc)pContext->alsa.snd_pcm_sw_params)((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams) != 0) { + if (((mal_snd_pcm_sw_params_proc)pContext->alsa.snd_pcm_sw_params)((mal_snd_pcm_t*)pDevice->alsa.pPCM, pSWParams) != 0) { mal_device_uninit__alsa(pDevice); - return mal_post_error(pDevice, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS); + return mal_post_error(pDevice, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed.", MAL_FAILED_TO_CONFIGURE_BACKEND_DEVICE); } // If we're _not_ using mmap we need to use an intermediary buffer. if (!pDevice->alsa.isUsingMMap) { - pDevice->alsa.pIntermediaryBuffer = mal_malloc(pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format)); + pDevice->alsa.pIntermediaryBuffer = mal_malloc(pDevice->bufferSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat)); if (pDevice->alsa.pIntermediaryBuffer == NULL) { mal_device_uninit__alsa(pDevice); return mal_post_error(pDevice, "[ALSA] Failed to allocate memory for intermediary buffer.", MAL_OUT_OF_MEMORY); } } - - // Grab the internal channel map. For now we're not going to bother trying to change the channel map and // instead just do it ourselves. - snd_pcm_chmap_t* pChmap = ((mal_snd_pcm_get_chmap_proc)pContext->alsa.snd_pcm_get_chmap)((snd_pcm_t*)pDevice->alsa.pPCM); + mal_snd_pcm_chmap_t* pChmap = ((mal_snd_pcm_get_chmap_proc)pContext->alsa.snd_pcm_get_chmap)((mal_snd_pcm_t*)pDevice->alsa.pPCM); if (pChmap != NULL) { // There are cases where the returned channel map can have a different channel count than was returned by snd_pcm_hw_params_set_channels_near(). if (pChmap->channels >= pDevice->internalChannels) { @@ -6620,7 +9058,7 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t // channels. If validation fails, fall back to defaults. // Fill with defaults. - mal_get_default_channel_mapping(pDevice->pContext->backend, pDevice->internalChannels, pDevice->internalChannelMap); + mal_get_standard_channel_map(mal_standard_channel_map_alsa, pDevice->internalChannels, pDevice->internalChannelMap); // Overwrite first pChmap->channels channels. for (mal_uint32 iChannel = 0; iChannel < pChmap->channels; ++iChannel) { @@ -6640,7 +9078,7 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t // If our channel map is invalid, fall back to defaults. if (!isValid) { - mal_get_default_channel_mapping(pDevice->pContext->backend, pDevice->internalChannels, pDevice->internalChannelMap); + mal_get_standard_channel_map(mal_standard_channel_map_alsa, pDevice->internalChannels, pDevice->internalChannelMap); } } @@ -6648,20 +9086,20 @@ static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type t pChmap = NULL; } else { // Could not retrieve the channel map. Fall back to a hard-coded assumption. - mal_get_default_channel_mapping(pDevice->pContext->backend, pDevice->internalChannels, pDevice->internalChannelMap); + mal_get_standard_channel_map(mal_standard_channel_map_alsa, pDevice->internalChannels, pDevice->internalChannelMap); } return MAL_SUCCESS; } -static mal_result mal_device__start_backend__alsa(mal_device* pDevice) +mal_result mal_device__start_backend__alsa(mal_device* pDevice) { mal_assert(pDevice != NULL); // Prepare the device first... - if (((mal_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)((snd_pcm_t*)pDevice->alsa.pPCM) < 0) { - return mal_post_error(pDevice, "[ALSA] Failed to prepare device.", MAL_ALSA_FAILED_TO_PREPARE_DEVICE); + if (((mal_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)((mal_snd_pcm_t*)pDevice->alsa.pPCM) < 0) { + return mal_post_error(pDevice, "[ALSA] Failed to prepare device.", MAL_FAILED_TO_START_BACKEND_DEVICE); } // ... and then grab an initial chunk from the client. After this is done, the device should @@ -6673,12 +9111,12 @@ static mal_result mal_device__start_backend__alsa(mal_device* pDevice) // mmap mode requires an explicit start. if (pDevice->alsa.isUsingMMap) { - if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((snd_pcm_t*)pDevice->alsa.pPCM) < 0) { + if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((mal_snd_pcm_t*)pDevice->alsa.pPCM) < 0) { return mal_post_error(pDevice, "[ALSA] Failed to start capture device.", MAL_FAILED_TO_START_BACKEND_DEVICE); } } } else { - if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((snd_pcm_t*)pDevice->alsa.pPCM) < 0) { + if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((mal_snd_pcm_t*)pDevice->alsa.pPCM) < 0) { return mal_post_error(pDevice, "[ALSA] Failed to start capture device.", MAL_FAILED_TO_START_BACKEND_DEVICE); } } @@ -6686,25 +9124,28 @@ static mal_result mal_device__start_backend__alsa(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__stop_backend__alsa(mal_device* pDevice) +mal_result mal_device__stop_backend__alsa(mal_device* pDevice) { mal_assert(pDevice != NULL); - ((mal_snd_pcm_drop_proc)pDevice->pContext->alsa.snd_pcm_drop)((snd_pcm_t*)pDevice->alsa.pPCM); + ((mal_snd_pcm_drop_proc)pDevice->pContext->alsa.snd_pcm_drop)((mal_snd_pcm_t*)pDevice->alsa.pPCM); return MAL_SUCCESS; } -static mal_result mal_device__break_main_loop__alsa(mal_device* pDevice) +mal_result mal_device__break_main_loop__alsa(mal_device* pDevice) { mal_assert(pDevice != NULL); - // Fallback. We just set a variable to tell the worker thread to terminate after handling the - // next bunch of frames. This is a slow way of handling this. + // First we tell the main loop that we're breaking... pDevice->alsa.breakFromMainLoop = MAL_TRUE; + + // Then we need to force snd_pcm_wait() to return. + //((mal_snd_pcm_drop_proc)pDevice->pContext->alsa.snd_pcm_drop)((mal_snd_pcm_t*)pDevice->alsa.pPCM); + return MAL_SUCCESS; } -static mal_result mal_device__main_loop__alsa(mal_device* pDevice) +mal_result mal_device__main_loop__alsa(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -6724,6 +9165,2199 @@ static mal_result mal_device__main_loop__alsa(mal_device* pDevice) #endif // ALSA + +/////////////////////////////////////////////////////////////////////////////// +// +// PulseAudio Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_HAS_PULSEAUDIO + +// It is assumed pulseaudio.h is available when compile-time linking is being used. We use this for type safety when using +// compile time linking (we don't have this luxury when using runtime linking without headers). +// +// When using compile time linking, each of our mal_* equivalents should use the sames types as defined by the header. The +// reason for this is that it allow us to take advantage of proper type safety. +#ifdef MAL_NO_RUNTIME_LINKING +#include + +#define MAL_PA_OK PA_OK +#define MAL_PA_ERR_ACCESS PA_ERR_ACCESS +#define MAL_PA_ERR_INVALID PA_ERR_INVALID +#define MAL_PA_ERR_NOENTITY PA_ERR_NOENTITY + +#define MAL_PA_CHANNELS_MAX PA_CHANNELS_MAX +#define MAL_PA_RATE_MAX PA_RATE_MAX + +typedef pa_context_flags_t mal_pa_context_flags_t; +#define MAL_PA_CONTEXT_NOFLAGS PA_CONTEXT_NOFLAGS +#define MAL_PA_CONTEXT_NOAUTOSPAWN PA_CONTEXT_NOAUTOSPAWN +#define MAL_PA_CONTEXT_NOFAIL PA_CONTEXT_NOFAIL + +typedef pa_stream_flags_t mal_pa_stream_flags_t; +#define MAL_PA_STREAM_NOFLAGS PA_STREAM_NOFLAGS +#define MAL_PA_STREAM_START_CORKED PA_STREAM_START_CORKED +#define MAL_PA_STREAM_INTERPOLATE_TIMING PA_STREAM_INTERPOLATE_TIMING +#define MAL_PA_STREAM_NOT_MONOTONIC PA_STREAM_NOT_MONOTONIC +#define MAL_PA_STREAM_AUTO_TIMING_UPDATE PA_STREAM_AUTO_TIMING_UPDATE +#define MAL_PA_STREAM_NO_REMAP_CHANNELS PA_STREAM_NO_REMAP_CHANNELS +#define MAL_PA_STREAM_NO_REMIX_CHANNELS PA_STREAM_NO_REMIX_CHANNELS +#define MAL_PA_STREAM_FIX_FORMAT PA_STREAM_FIX_FORMAT +#define MAL_PA_STREAM_FIX_RATE PA_STREAM_FIX_RATE +#define MAL_PA_STREAM_FIX_CHANNELS PA_STREAM_FIX_CHANNELS +#define MAL_PA_STREAM_DONT_MOVE PA_STREAM_DONT_MOVE +#define MAL_PA_STREAM_VARIABLE_RATE PA_STREAM_VARIABLE_RATE +#define MAL_PA_STREAM_PEAK_DETECT PA_STREAM_PEAK_DETECT +#define MAL_PA_STREAM_START_MUTED PA_STREAM_START_MUTED +#define MAL_PA_STREAM_ADJUST_LATENCY PA_STREAM_ADJUST_LATENCY +#define MAL_PA_STREAM_EARLY_REQUESTS PA_STREAM_EARLY_REQUESTS +#define MAL_PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND +#define MAL_PA_STREAM_START_UNMUTED PA_STREAM_START_UNMUTED +#define MAL_PA_STREAM_FAIL_ON_SUSPEND PA_STREAM_FAIL_ON_SUSPEND +#define MAL_PA_STREAM_RELATIVE_VOLUME PA_STREAM_RELATIVE_VOLUME +#define MAL_PA_STREAM_PASSTHROUGH PA_STREAM_PASSTHROUGH + +typedef pa_sink_flags_t mal_pa_sink_flags_t; +#define MAL_PA_SINK_NOFLAGS PA_SINK_NOFLAGS +#define MAL_PA_SINK_HW_VOLUME_CTRL PA_SINK_HW_VOLUME_CTRL +#define MAL_PA_SINK_LATENCY PA_SINK_LATENCY +#define MAL_PA_SINK_HARDWARE PA_SINK_HARDWARE +#define MAL_PA_SINK_NETWORK PA_SINK_NETWORK +#define MAL_PA_SINK_HW_MUTE_CTRL PA_SINK_HW_MUTE_CTRL +#define MAL_PA_SINK_DECIBEL_VOLUME PA_SINK_DECIBEL_VOLUME +#define MAL_PA_SINK_FLAT_VOLUME PA_SINK_FLAT_VOLUME +#define MAL_PA_SINK_DYNAMIC_LATENCY PA_SINK_DYNAMIC_LATENCY +#define MAL_PA_SINK_SET_FORMATS PA_SINK_SET_FORMATS + +typedef pa_source_flags_t mal_pa_source_flags_t; +#define MAL_PA_SOURCE_NOFLAGS PA_SOURCE_NOFLAGS +#define MAL_PA_SOURCE_HW_VOLUME_CTRL PA_SOURCE_HW_VOLUME_CTRL +#define MAL_PA_SOURCE_LATENCY PA_SOURCE_LATENCY +#define MAL_PA_SOURCE_HARDWARE PA_SOURCE_HARDWARE +#define MAL_PA_SOURCE_NETWORK PA_SOURCE_NETWORK +#define MAL_PA_SOURCE_HW_MUTE_CTRL PA_SOURCE_HW_MUTE_CTRL +#define MAL_PA_SOURCE_DECIBEL_VOLUME PA_SOURCE_DECIBEL_VOLUME +#define MAL_PA_SOURCE_DYNAMIC_LATENCY PA_SOURCE_DYNAMIC_LATENCY +#define MAL_PA_SOURCE_FLAT_VOLUME PA_SOURCE_FLAT_VOLUME + +typedef pa_context_state_t mal_pa_context_state_t; +#define MAL_PA_CONTEXT_UNCONNECTED PA_CONTEXT_UNCONNECTED +#define MAL_PA_CONTEXT_CONNECTING PA_CONTEXT_CONNECTING +#define MAL_PA_CONTEXT_AUTHORIZING PA_CONTEXT_AUTHORIZING +#define MAL_PA_CONTEXT_SETTING_NAME PA_CONTEXT_SETTING_NAME +#define MAL_PA_CONTEXT_READY PA_CONTEXT_READY +#define MAL_PA_CONTEXT_FAILED PA_CONTEXT_FAILED +#define MAL_PA_CONTEXT_TERMINATED PA_CONTEXT_TERMINATED + +typedef pa_stream_state_t mal_pa_stream_state_t; +#define MAL_PA_STREAM_UNCONNECTED PA_STREAM_UNCONNECTED +#define MAL_PA_STREAM_CREATING PA_STREAM_CREATING +#define MAL_PA_STREAM_READY PA_STREAM_READY +#define MAL_PA_STREAM_FAILED PA_STREAM_FAILED +#define MAL_PA_STREAM_TERMINATED PA_STREAM_TERMINATED + +typedef pa_operation_state_t mal_pa_operation_state_t; +#define MAL_PA_OPERATION_RUNNING PA_OPERATION_RUNNING +#define MAL_PA_OPERATION_DONE PA_OPERATION_DONE +#define MAL_PA_OPERATION_CANCELLED PA_OPERATION_CANCELLED + +typedef pa_sink_state_t mal_pa_sink_state_t; +#define MAL_PA_SINK_INVALID_STATE PA_SINK_INVALID_STATE +#define MAL_PA_SINK_RUNNING PA_SINK_RUNNING +#define MAL_PA_SINK_IDLE PA_SINK_IDLE +#define MAL_PA_SINK_SUSPENDED PA_SINK_SUSPENDED + +typedef pa_source_state_t mal_pa_source_state_t; +#define MAL_PA_SOURCE_INVALID_STATE PA_SOURCE_INVALID_STATE +#define MAL_PA_SOURCE_RUNNING PA_SOURCE_RUNNING +#define MAL_PA_SOURCE_IDLE PA_SOURCE_IDLE +#define MAL_PA_SOURCE_SUSPENDED PA_SOURCE_SUSPENDED + +typedef pa_seek_mode_t mal_pa_seek_mode_t; +#define MAL_PA_SEEK_RELATIVE PA_SEEK_RELATIVE +#define MAL_PA_SEEK_ABSOLUTE PA_SEEK_ABSOLUTE +#define MAL_PA_SEEK_RELATIVE_ON_READ PA_SEEK_RELATIVE_ON_READ +#define MAL_PA_SEEK_RELATIVE_END PA_SEEK_RELATIVE_END + +typedef pa_channel_position_t mal_pa_channel_position_t; +#define MAL_PA_CHANNEL_POSITION_INVALID PA_CHANNEL_POSITION_INVALID +#define MAL_PA_CHANNEL_POSITION_MONO PA_CHANNEL_POSITION_MONO +#define MAL_PA_CHANNEL_POSITION_FRONT_LEFT PA_CHANNEL_POSITION_FRONT_LEFT +#define MAL_PA_CHANNEL_POSITION_FRONT_RIGHT PA_CHANNEL_POSITION_FRONT_RIGHT +#define MAL_PA_CHANNEL_POSITION_FRONT_CENTER PA_CHANNEL_POSITION_FRONT_CENTER +#define MAL_PA_CHANNEL_POSITION_REAR_CENTER PA_CHANNEL_POSITION_REAR_CENTER +#define MAL_PA_CHANNEL_POSITION_REAR_LEFT PA_CHANNEL_POSITION_REAR_LEFT +#define MAL_PA_CHANNEL_POSITION_REAR_RIGHT PA_CHANNEL_POSITION_REAR_RIGHT +#define MAL_PA_CHANNEL_POSITION_LFE PA_CHANNEL_POSITION_LFE +#define MAL_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER +#define MAL_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER +#define MAL_PA_CHANNEL_POSITION_SIDE_LEFT PA_CHANNEL_POSITION_SIDE_LEFT +#define MAL_PA_CHANNEL_POSITION_SIDE_RIGHT PA_CHANNEL_POSITION_SIDE_RIGHT +#define MAL_PA_CHANNEL_POSITION_AUX0 PA_CHANNEL_POSITION_AUX0 +#define MAL_PA_CHANNEL_POSITION_AUX1 PA_CHANNEL_POSITION_AUX1 +#define MAL_PA_CHANNEL_POSITION_AUX2 PA_CHANNEL_POSITION_AUX2 +#define MAL_PA_CHANNEL_POSITION_AUX3 PA_CHANNEL_POSITION_AUX3 +#define MAL_PA_CHANNEL_POSITION_AUX4 PA_CHANNEL_POSITION_AUX4 +#define MAL_PA_CHANNEL_POSITION_AUX5 PA_CHANNEL_POSITION_AUX5 +#define MAL_PA_CHANNEL_POSITION_AUX6 PA_CHANNEL_POSITION_AUX6 +#define MAL_PA_CHANNEL_POSITION_AUX7 PA_CHANNEL_POSITION_AUX7 +#define MAL_PA_CHANNEL_POSITION_AUX8 PA_CHANNEL_POSITION_AUX8 +#define MAL_PA_CHANNEL_POSITION_AUX9 PA_CHANNEL_POSITION_AUX9 +#define MAL_PA_CHANNEL_POSITION_AUX10 PA_CHANNEL_POSITION_AUX10 +#define MAL_PA_CHANNEL_POSITION_AUX11 PA_CHANNEL_POSITION_AUX11 +#define MAL_PA_CHANNEL_POSITION_AUX12 PA_CHANNEL_POSITION_AUX12 +#define MAL_PA_CHANNEL_POSITION_AUX13 PA_CHANNEL_POSITION_AUX13 +#define MAL_PA_CHANNEL_POSITION_AUX14 PA_CHANNEL_POSITION_AUX14 +#define MAL_PA_CHANNEL_POSITION_AUX15 PA_CHANNEL_POSITION_AUX15 +#define MAL_PA_CHANNEL_POSITION_AUX16 PA_CHANNEL_POSITION_AUX16 +#define MAL_PA_CHANNEL_POSITION_AUX17 PA_CHANNEL_POSITION_AUX17 +#define MAL_PA_CHANNEL_POSITION_AUX18 PA_CHANNEL_POSITION_AUX18 +#define MAL_PA_CHANNEL_POSITION_AUX19 PA_CHANNEL_POSITION_AUX19 +#define MAL_PA_CHANNEL_POSITION_AUX20 PA_CHANNEL_POSITION_AUX20 +#define MAL_PA_CHANNEL_POSITION_AUX21 PA_CHANNEL_POSITION_AUX21 +#define MAL_PA_CHANNEL_POSITION_AUX22 PA_CHANNEL_POSITION_AUX22 +#define MAL_PA_CHANNEL_POSITION_AUX23 PA_CHANNEL_POSITION_AUX23 +#define MAL_PA_CHANNEL_POSITION_AUX24 PA_CHANNEL_POSITION_AUX24 +#define MAL_PA_CHANNEL_POSITION_AUX25 PA_CHANNEL_POSITION_AUX25 +#define MAL_PA_CHANNEL_POSITION_AUX26 PA_CHANNEL_POSITION_AUX26 +#define MAL_PA_CHANNEL_POSITION_AUX27 PA_CHANNEL_POSITION_AUX27 +#define MAL_PA_CHANNEL_POSITION_AUX28 PA_CHANNEL_POSITION_AUX28 +#define MAL_PA_CHANNEL_POSITION_AUX29 PA_CHANNEL_POSITION_AUX29 +#define MAL_PA_CHANNEL_POSITION_AUX30 PA_CHANNEL_POSITION_AUX30 +#define MAL_PA_CHANNEL_POSITION_AUX31 PA_CHANNEL_POSITION_AUX31 +#define MAL_PA_CHANNEL_POSITION_TOP_CENTER PA_CHANNEL_POSITION_TOP_CENTER +#define MAL_PA_CHANNEL_POSITION_TOP_FRONT_LEFT PA_CHANNEL_POSITION_TOP_FRONT_LEFT +#define MAL_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT PA_CHANNEL_POSITION_TOP_FRONT_RIGHT +#define MAL_PA_CHANNEL_POSITION_TOP_FRONT_CENTER PA_CHANNEL_POSITION_TOP_FRONT_CENTER +#define MAL_PA_CHANNEL_POSITION_TOP_REAR_LEFT PA_CHANNEL_POSITION_TOP_REAR_LEFT +#define MAL_PA_CHANNEL_POSITION_TOP_REAR_RIGHT PA_CHANNEL_POSITION_TOP_REAR_RIGHT +#define MAL_PA_CHANNEL_POSITION_TOP_REAR_CENTER PA_CHANNEL_POSITION_TOP_REAR_CENTER +#define MAL_PA_CHANNEL_POSITION_LEFT PA_CHANNEL_POSITION_LEFT +#define MAL_PA_CHANNEL_POSITION_RIGHT PA_CHANNEL_POSITION_RIGHT +#define MAL_PA_CHANNEL_POSITION_CENTER PA_CHANNEL_POSITION_CENTER +#define MAL_PA_CHANNEL_POSITION_SUBWOOFER PA_CHANNEL_POSITION_SUBWOOFER + +typedef pa_channel_map_def_t mal_pa_channel_map_def_t; +#define MAL_PA_CHANNEL_MAP_AIFF PA_CHANNEL_MAP_AIFF +#define MAL_PA_CHANNEL_MAP_ALSA PA_CHANNEL_MAP_ALSA +#define MAL_PA_CHANNEL_MAP_AUX PA_CHANNEL_MAP_AUX +#define MAL_PA_CHANNEL_MAP_WAVEEX PA_CHANNEL_MAP_WAVEEX +#define MAL_PA_CHANNEL_MAP_OSS PA_CHANNEL_MAP_OSS +#define MAL_PA_CHANNEL_MAP_DEFAULT PA_CHANNEL_MAP_DEFAULT + +typedef pa_sample_format_t mal_pa_sample_format_t; +#define MAL_PA_SAMPLE_INVALID PA_SAMPLE_INVALID +#define MAL_PA_SAMPLE_U8 PA_SAMPLE_U8 +#define MAL_PA_SAMPLE_ALAW PA_SAMPLE_ALAW +#define MAL_PA_SAMPLE_ULAW PA_SAMPLE_ULAW +#define MAL_PA_SAMPLE_S16LE PA_SAMPLE_S16LE +#define MAL_PA_SAMPLE_S16BE PA_SAMPLE_S16BE +#define MAL_PA_SAMPLE_FLOAT32LE PA_SAMPLE_FLOAT32LE +#define MAL_PA_SAMPLE_FLOAT32BE PA_SAMPLE_FLOAT32BE +#define MAL_PA_SAMPLE_S32LE PA_SAMPLE_S32LE +#define MAL_PA_SAMPLE_S32BE PA_SAMPLE_S32BE +#define MAL_PA_SAMPLE_S24LE PA_SAMPLE_S24LE +#define MAL_PA_SAMPLE_S24BE PA_SAMPLE_S24BE +#define MAL_PA_SAMPLE_S24_32LE PA_SAMPLE_S24_32LE +#define MAL_PA_SAMPLE_S24_32BE PA_SAMPLE_S24_32BE + +typedef pa_mainloop mal_pa_mainloop; +typedef pa_mainloop_api mal_pa_mainloop_api; +typedef pa_context mal_pa_context; +typedef pa_operation mal_pa_operation; +typedef pa_stream mal_pa_stream; +typedef pa_spawn_api mal_pa_spawn_api; +typedef pa_buffer_attr mal_pa_buffer_attr; +typedef pa_channel_map mal_pa_channel_map; +typedef pa_cvolume mal_pa_cvolume; +typedef pa_sample_spec mal_pa_sample_spec; +typedef pa_sink_info mal_pa_sink_info; +typedef pa_source_info mal_pa_source_info; + +typedef pa_context_notify_cb_t mal_pa_context_notify_cb_t; +typedef pa_sink_info_cb_t mal_pa_sink_info_cb_t; +typedef pa_source_info_cb_t mal_pa_source_info_cb_t; +typedef pa_stream_success_cb_t mal_pa_stream_success_cb_t; +typedef pa_stream_request_cb_t mal_pa_stream_request_cb_t; +typedef pa_free_cb_t mal_pa_free_cb_t; +#else +#define MAL_PA_OK 0 +#define MAL_PA_ERR_ACCESS 1 +#define MAL_PA_ERR_INVALID 2 +#define MAL_PA_ERR_NOENTITY 5 + +#define MAL_PA_CHANNELS_MAX 32 +#define MAL_PA_RATE_MAX 384000 + +typedef int mal_pa_context_flags_t; +#define MAL_PA_CONTEXT_NOFLAGS 0x00000000 +#define MAL_PA_CONTEXT_NOAUTOSPAWN 0x00000001 +#define MAL_PA_CONTEXT_NOFAIL 0x00000002 + +typedef int mal_pa_stream_flags_t; +#define MAL_PA_STREAM_NOFLAGS 0x00000000 +#define MAL_PA_STREAM_START_CORKED 0x00000001 +#define MAL_PA_STREAM_INTERPOLATE_TIMING 0x00000002 +#define MAL_PA_STREAM_NOT_MONOTONIC 0x00000004 +#define MAL_PA_STREAM_AUTO_TIMING_UPDATE 0x00000008 +#define MAL_PA_STREAM_NO_REMAP_CHANNELS 0x00000010 +#define MAL_PA_STREAM_NO_REMIX_CHANNELS 0x00000020 +#define MAL_PA_STREAM_FIX_FORMAT 0x00000040 +#define MAL_PA_STREAM_FIX_RATE 0x00000080 +#define MAL_PA_STREAM_FIX_CHANNELS 0x00000100 +#define MAL_PA_STREAM_DONT_MOVE 0x00000200 +#define MAL_PA_STREAM_VARIABLE_RATE 0x00000400 +#define MAL_PA_STREAM_PEAK_DETECT 0x00000800 +#define MAL_PA_STREAM_START_MUTED 0x00001000 +#define MAL_PA_STREAM_ADJUST_LATENCY 0x00002000 +#define MAL_PA_STREAM_EARLY_REQUESTS 0x00004000 +#define MAL_PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND 0x00008000 +#define MAL_PA_STREAM_START_UNMUTED 0x00010000 +#define MAL_PA_STREAM_FAIL_ON_SUSPEND 0x00020000 +#define MAL_PA_STREAM_RELATIVE_VOLUME 0x00040000 +#define MAL_PA_STREAM_PASSTHROUGH 0x00080000 + +typedef int mal_pa_sink_flags_t; +#define MAL_PA_SINK_NOFLAGS 0x00000000 +#define MAL_PA_SINK_HW_VOLUME_CTRL 0x00000001 +#define MAL_PA_SINK_LATENCY 0x00000002 +#define MAL_PA_SINK_HARDWARE 0x00000004 +#define MAL_PA_SINK_NETWORK 0x00000008 +#define MAL_PA_SINK_HW_MUTE_CTRL 0x00000010 +#define MAL_PA_SINK_DECIBEL_VOLUME 0x00000020 +#define MAL_PA_SINK_FLAT_VOLUME 0x00000040 +#define MAL_PA_SINK_DYNAMIC_LATENCY 0x00000080 +#define MAL_PA_SINK_SET_FORMATS 0x00000100 + +typedef int mal_pa_source_flags_t; +#define MAL_PA_SOURCE_NOFLAGS 0x00000000 +#define MAL_PA_SOURCE_HW_VOLUME_CTRL 0x00000001 +#define MAL_PA_SOURCE_LATENCY 0x00000002 +#define MAL_PA_SOURCE_HARDWARE 0x00000004 +#define MAL_PA_SOURCE_NETWORK 0x00000008 +#define MAL_PA_SOURCE_HW_MUTE_CTRL 0x00000010 +#define MAL_PA_SOURCE_DECIBEL_VOLUME 0x00000020 +#define MAL_PA_SOURCE_DYNAMIC_LATENCY 0x00000040 +#define MAL_PA_SOURCE_FLAT_VOLUME 0x00000080 + +typedef int mal_pa_context_state_t; +#define MAL_PA_CONTEXT_UNCONNECTED 0 +#define MAL_PA_CONTEXT_CONNECTING 1 +#define MAL_PA_CONTEXT_AUTHORIZING 2 +#define MAL_PA_CONTEXT_SETTING_NAME 3 +#define MAL_PA_CONTEXT_READY 4 +#define MAL_PA_CONTEXT_FAILED 5 +#define MAL_PA_CONTEXT_TERMINATED 6 + +typedef int mal_pa_stream_state_t; +#define MAL_PA_STREAM_UNCONNECTED 0 +#define MAL_PA_STREAM_CREATING 1 +#define MAL_PA_STREAM_READY 2 +#define MAL_PA_STREAM_FAILED 3 +#define MAL_PA_STREAM_TERMINATED 4 + +typedef int mal_pa_operation_state_t; +#define MAL_PA_OPERATION_RUNNING 0 +#define MAL_PA_OPERATION_DONE 1 +#define MAL_PA_OPERATION_CANCELLED 2 + +typedef int mal_pa_sink_state_t; +#define MAL_PA_SINK_INVALID_STATE -1 +#define MAL_PA_SINK_RUNNING 0 +#define MAL_PA_SINK_IDLE 1 +#define MAL_PA_SINK_SUSPENDED 2 + +typedef int mal_pa_source_state_t; +#define MAL_PA_SOURCE_INVALID_STATE -1 +#define MAL_PA_SOURCE_RUNNING 0 +#define MAL_PA_SOURCE_IDLE 1 +#define MAL_PA_SOURCE_SUSPENDED 2 + +typedef int mal_pa_seek_mode_t; +#define MAL_PA_SEEK_RELATIVE 0 +#define MAL_PA_SEEK_ABSOLUTE 1 +#define MAL_PA_SEEK_RELATIVE_ON_READ 2 +#define MAL_PA_SEEK_RELATIVE_END 3 + +typedef int mal_pa_channel_position_t; +#define MAL_PA_CHANNEL_POSITION_INVALID -1 +#define MAL_PA_CHANNEL_POSITION_MONO 0 +#define MAL_PA_CHANNEL_POSITION_FRONT_LEFT 1 +#define MAL_PA_CHANNEL_POSITION_FRONT_RIGHT 2 +#define MAL_PA_CHANNEL_POSITION_FRONT_CENTER 3 +#define MAL_PA_CHANNEL_POSITION_REAR_CENTER 4 +#define MAL_PA_CHANNEL_POSITION_REAR_LEFT 5 +#define MAL_PA_CHANNEL_POSITION_REAR_RIGHT 6 +#define MAL_PA_CHANNEL_POSITION_LFE 7 +#define MAL_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER 8 +#define MAL_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER 9 +#define MAL_PA_CHANNEL_POSITION_SIDE_LEFT 10 +#define MAL_PA_CHANNEL_POSITION_SIDE_RIGHT 11 +#define MAL_PA_CHANNEL_POSITION_AUX0 12 +#define MAL_PA_CHANNEL_POSITION_AUX1 13 +#define MAL_PA_CHANNEL_POSITION_AUX2 14 +#define MAL_PA_CHANNEL_POSITION_AUX3 15 +#define MAL_PA_CHANNEL_POSITION_AUX4 16 +#define MAL_PA_CHANNEL_POSITION_AUX5 17 +#define MAL_PA_CHANNEL_POSITION_AUX6 18 +#define MAL_PA_CHANNEL_POSITION_AUX7 19 +#define MAL_PA_CHANNEL_POSITION_AUX8 20 +#define MAL_PA_CHANNEL_POSITION_AUX9 21 +#define MAL_PA_CHANNEL_POSITION_AUX10 22 +#define MAL_PA_CHANNEL_POSITION_AUX11 23 +#define MAL_PA_CHANNEL_POSITION_AUX12 24 +#define MAL_PA_CHANNEL_POSITION_AUX13 25 +#define MAL_PA_CHANNEL_POSITION_AUX14 26 +#define MAL_PA_CHANNEL_POSITION_AUX15 27 +#define MAL_PA_CHANNEL_POSITION_AUX16 28 +#define MAL_PA_CHANNEL_POSITION_AUX17 29 +#define MAL_PA_CHANNEL_POSITION_AUX18 30 +#define MAL_PA_CHANNEL_POSITION_AUX19 31 +#define MAL_PA_CHANNEL_POSITION_AUX20 32 +#define MAL_PA_CHANNEL_POSITION_AUX21 33 +#define MAL_PA_CHANNEL_POSITION_AUX22 34 +#define MAL_PA_CHANNEL_POSITION_AUX23 35 +#define MAL_PA_CHANNEL_POSITION_AUX24 36 +#define MAL_PA_CHANNEL_POSITION_AUX25 37 +#define MAL_PA_CHANNEL_POSITION_AUX26 38 +#define MAL_PA_CHANNEL_POSITION_AUX27 39 +#define MAL_PA_CHANNEL_POSITION_AUX28 40 +#define MAL_PA_CHANNEL_POSITION_AUX29 41 +#define MAL_PA_CHANNEL_POSITION_AUX30 42 +#define MAL_PA_CHANNEL_POSITION_AUX31 43 +#define MAL_PA_CHANNEL_POSITION_TOP_CENTER 44 +#define MAL_PA_CHANNEL_POSITION_TOP_FRONT_LEFT 45 +#define MAL_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT 46 +#define MAL_PA_CHANNEL_POSITION_TOP_FRONT_CENTER 47 +#define MAL_PA_CHANNEL_POSITION_TOP_REAR_LEFT 48 +#define MAL_PA_CHANNEL_POSITION_TOP_REAR_RIGHT 49 +#define MAL_PA_CHANNEL_POSITION_TOP_REAR_CENTER 50 +#define MAL_PA_CHANNEL_POSITION_LEFT MAL_PA_CHANNEL_POSITION_FRONT_LEFT +#define MAL_PA_CHANNEL_POSITION_RIGHT MAL_PA_CHANNEL_POSITION_FRONT_RIGHT +#define MAL_PA_CHANNEL_POSITION_CENTER MAL_PA_CHANNEL_POSITION_FRONT_CENTER +#define MAL_PA_CHANNEL_POSITION_SUBWOOFER MAL_PA_CHANNEL_POSITION_LFE + +typedef int mal_pa_channel_map_def_t; +#define MAL_PA_CHANNEL_MAP_AIFF 0 +#define MAL_PA_CHANNEL_MAP_ALSA 1 +#define MAL_PA_CHANNEL_MAP_AUX 2 +#define MAL_PA_CHANNEL_MAP_WAVEEX 3 +#define MAL_PA_CHANNEL_MAP_OSS 4 +#define MAL_PA_CHANNEL_MAP_DEFAULT MAL_PA_CHANNEL_MAP_AIFF + +typedef int mal_pa_sample_format_t; +#define MAL_PA_SAMPLE_INVALID -1 +#define MAL_PA_SAMPLE_U8 0 +#define MAL_PA_SAMPLE_ALAW 1 +#define MAL_PA_SAMPLE_ULAW 2 +#define MAL_PA_SAMPLE_S16LE 3 +#define MAL_PA_SAMPLE_S16BE 4 +#define MAL_PA_SAMPLE_FLOAT32LE 5 +#define MAL_PA_SAMPLE_FLOAT32BE 6 +#define MAL_PA_SAMPLE_S32LE 7 +#define MAL_PA_SAMPLE_S32BE 8 +#define MAL_PA_SAMPLE_S24LE 9 +#define MAL_PA_SAMPLE_S24BE 10 +#define MAL_PA_SAMPLE_S24_32LE 11 +#define MAL_PA_SAMPLE_S24_32BE 12 + +typedef struct mal_pa_mainloop mal_pa_mainloop; +typedef struct mal_pa_mainloop_api mal_pa_mainloop_api; +typedef struct mal_pa_context mal_pa_context; +typedef struct mal_pa_operation mal_pa_operation; +typedef struct mal_pa_stream mal_pa_stream; +typedef struct mal_pa_spawn_api mal_pa_spawn_api; + +typedef struct +{ + mal_uint32 maxlength; + mal_uint32 tlength; + mal_uint32 prebuf; + mal_uint32 minreq; + mal_uint32 fragsize; +} mal_pa_buffer_attr; + +typedef struct +{ + mal_uint8 channels; + mal_pa_channel_position_t map[MAL_PA_CHANNELS_MAX]; +} mal_pa_channel_map; + +typedef struct +{ + mal_uint8 channels; + mal_uint32 values[MAL_PA_CHANNELS_MAX]; +} mal_pa_cvolume; + +typedef struct +{ + mal_pa_sample_format_t format; + mal_uint32 rate; + mal_uint8 channels; +} mal_pa_sample_spec; + +typedef struct +{ + const char* name; + mal_uint32 index; + const char* description; + mal_pa_sample_spec sample_spec; + mal_pa_channel_map channel_map; + mal_uint32 owner_module; + mal_pa_cvolume volume; + int mute; + mal_uint32 monitor_source; + const char* monitor_source_name; + mal_uint64 latency; + const char* driver; + mal_pa_sink_flags_t flags; + void* proplist; + mal_uint64 configured_latency; + mal_uint32 base_volume; + mal_pa_sink_state_t state; + mal_uint32 n_volume_steps; + mal_uint32 card; + mal_uint32 n_ports; + void** ports; + void* active_port; + mal_uint8 n_formats; + void** formats; +} mal_pa_sink_info; + +typedef struct +{ + const char *name; + mal_uint32 index; + const char *description; + mal_pa_sample_spec sample_spec; + mal_pa_channel_map channel_map; + mal_uint32 owner_module; + mal_pa_cvolume volume; + int mute; + mal_uint32 monitor_of_sink; + const char *monitor_of_sink_name; + mal_uint64 latency; + const char *driver; + mal_pa_source_flags_t flags; + void* proplist; + mal_uint64 configured_latency; + mal_uint32 base_volume; + mal_pa_source_state_t state; + mal_uint32 n_volume_steps; + mal_uint32 card; + mal_uint32 n_ports; + void** ports; + void* active_port; + mal_uint8 n_formats; + void** formats; +} mal_pa_source_info; + +typedef void (* mal_pa_context_notify_cb_t)(mal_pa_context* c, void* userdata); +typedef void (* mal_pa_sink_info_cb_t) (mal_pa_context* c, const mal_pa_sink_info* i, int eol, void* userdata); +typedef void (* mal_pa_source_info_cb_t) (mal_pa_context* c, const mal_pa_source_info* i, int eol, void* userdata); +typedef void (* mal_pa_stream_success_cb_t)(mal_pa_stream* s, int success, void* userdata); +typedef void (* mal_pa_stream_request_cb_t)(mal_pa_stream* s, size_t nbytes, void* userdata); +typedef void (* mal_pa_free_cb_t) (void* p); +#endif + + +typedef mal_pa_mainloop* (* mal_pa_mainloop_new_proc) (); +typedef void (* mal_pa_mainloop_free_proc) (mal_pa_mainloop* m); +typedef mal_pa_mainloop_api* (* mal_pa_mainloop_get_api_proc) (mal_pa_mainloop* m); +typedef int (* mal_pa_mainloop_iterate_proc) (mal_pa_mainloop* m, int block, int* retval); +typedef void (* mal_pa_mainloop_wakeup_proc) (mal_pa_mainloop* m); +typedef mal_pa_context* (* mal_pa_context_new_proc) (mal_pa_mainloop_api* mainloop, const char* name); +typedef void (* mal_pa_context_unref_proc) (mal_pa_context* c); +typedef int (* mal_pa_context_connect_proc) (mal_pa_context* c, const char* server, mal_pa_context_flags_t flags, const mal_pa_spawn_api* api); +typedef void (* mal_pa_context_disconnect_proc) (mal_pa_context* c); +typedef void (* mal_pa_context_set_state_callback_proc) (mal_pa_context* c, mal_pa_context_notify_cb_t cb, void* userdata); +typedef mal_pa_context_state_t (* mal_pa_context_get_state_proc) (mal_pa_context* c); +typedef mal_pa_operation* (* mal_pa_context_get_sink_info_list_proc) (mal_pa_context* c, mal_pa_sink_info_cb_t cb, void* userdata); +typedef mal_pa_operation* (* mal_pa_context_get_source_info_list_proc) (mal_pa_context* c, mal_pa_source_info_cb_t cb, void* userdata); +typedef mal_pa_operation* (* mal_pa_context_get_sink_info_by_name_proc) (mal_pa_context* c, const char* name, mal_pa_sink_info_cb_t cb, void* userdata); +typedef mal_pa_operation* (* mal_pa_context_get_source_info_by_name_proc)(mal_pa_context* c, const char* name, mal_pa_source_info_cb_t cb, void* userdata); +typedef void (* mal_pa_operation_unref_proc) (mal_pa_operation* o); +typedef mal_pa_operation_state_t (* mal_pa_operation_get_state_proc) (mal_pa_operation* o); +typedef mal_pa_channel_map* (* mal_pa_channel_map_init_extend_proc) (mal_pa_channel_map* m, unsigned channels, mal_pa_channel_map_def_t def); +typedef int (* mal_pa_channel_map_valid_proc) (const mal_pa_channel_map* m); +typedef int (* mal_pa_channel_map_compatible_proc) (const mal_pa_channel_map* m, const mal_pa_sample_spec* ss); +typedef mal_pa_stream* (* mal_pa_stream_new_proc) (mal_pa_context* c, const char* name, const mal_pa_sample_spec* ss, const mal_pa_channel_map* map); +typedef void (* mal_pa_stream_unref_proc) (mal_pa_stream* s); +typedef int (* mal_pa_stream_connect_playback_proc) (mal_pa_stream* s, const char* dev, const mal_pa_buffer_attr* attr, mal_pa_stream_flags_t flags, const mal_pa_cvolume* volume, mal_pa_stream* sync_stream); +typedef int (* mal_pa_stream_connect_record_proc) (mal_pa_stream* s, const char* dev, const mal_pa_buffer_attr* attr, mal_pa_stream_flags_t flags); +typedef int (* mal_pa_stream_disconnect_proc) (mal_pa_stream* s); +typedef mal_pa_stream_state_t (* mal_pa_stream_get_state_proc) (mal_pa_stream* s); +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 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); +typedef mal_pa_operation* (* mal_pa_stream_flush_proc) (mal_pa_stream* s, mal_pa_stream_success_cb_t cb, void* userdata); +typedef mal_pa_operation* (* mal_pa_stream_drain_proc) (mal_pa_stream* s, mal_pa_stream_success_cb_t cb, void* userdata); +typedef mal_pa_operation* (* mal_pa_stream_cork_proc) (mal_pa_stream* s, int b, mal_pa_stream_success_cb_t cb, void* userdata); +typedef mal_pa_operation* (* mal_pa_stream_trigger_proc) (mal_pa_stream* s, mal_pa_stream_success_cb_t cb, void* userdata); +typedef int (* mal_pa_stream_begin_write_proc) (mal_pa_stream* s, void** data, size_t* nbytes); +typedef int (* mal_pa_stream_write_proc) (mal_pa_stream* s, const void* data, size_t nbytes, mal_pa_free_cb_t free_cb, int64_t offset, mal_pa_seek_mode_t seek); +typedef int (* mal_pa_stream_peek_proc) (mal_pa_stream* s, const void** data, size_t* nbytes); +typedef int (* mal_pa_stream_drop_proc) (mal_pa_stream* s); + +typedef struct +{ + mal_uint32 count; + mal_uint32 capacity; + mal_device_info* pInfo; +} mal_pulse_device_enum_data; + +mal_result mal_result_from_pulse(int result) +{ + switch (result) { + case MAL_PA_OK: return MAL_SUCCESS; + case MAL_PA_ERR_ACCESS: return MAL_ACCESS_DENIED; + case MAL_PA_ERR_INVALID: return MAL_INVALID_ARGS; + case MAL_PA_ERR_NOENTITY: return MAL_NO_DEVICE; + default: return MAL_ERROR; + } +} + +#if 0 +mal_pa_sample_format_t mal_format_to_pulse(mal_format format) +{ + switch (format) + { + case mal_format_u8: return MAL_PA_SAMPLE_U8; + case mal_format_s16: return MAL_PA_SAMPLE_S16LE; + //case mal_format_s16be: return MAL_PA_SAMPLE_S16BE; + case mal_format_s24: return MAL_PA_SAMPLE_S24LE; + //case mal_format_s24be: return MAL_PA_SAMPLE_S24BE; + //case mal_format_s24_32: return MAL_PA_SAMPLE_S24_32LE; + //case mal_format_s24_32be: return MAL_PA_SAMPLE_S24_32BE; + case mal_format_s32: return MAL_PA_SAMPLE_S32LE; + //case mal_format_s32be: return MAL_PA_SAMPLE_S32BE; + case mal_format_f32: return MAL_PA_SAMPLE_FLOAT32LE; + //case mal_format_f32be: return PA_SAMPLE_FLOAT32BE; + + default: return MAL_PA_SAMPLE_INVALID; + } +} +#endif + +mal_format mal_format_from_pulse(mal_pa_sample_format_t format) +{ + switch (format) + { + case MAL_PA_SAMPLE_U8: return mal_format_u8; + case MAL_PA_SAMPLE_S16LE: return mal_format_s16; + //case MAL_PA_SAMPLE_S16BE: return mal_format_s16be; + case MAL_PA_SAMPLE_S24LE: return mal_format_s24; + //case MAL_PA_SAMPLE_S24BE: return mal_format_s24be; + //case MAL_PA_SAMPLE_S24_32LE: return mal_format_s24_32; + //case MAL_PA_SAMPLE_S24_32BE: return mal_format_s24_32be; + case MAL_PA_SAMPLE_S32LE: return mal_format_s32; + //case MAL_PA_SAMPLE_S32BE: return mal_format_s32be; + case MAL_PA_SAMPLE_FLOAT32LE: return mal_format_f32; + //case MAL_PA_SAMPLE_FLOAT32BE: return mal_format_f32be; + + default: return mal_format_unknown; + } +} + +mal_channel mal_channel_position_from_pulse(mal_pa_channel_position_t position) +{ + switch (position) + { + case MAL_PA_CHANNEL_POSITION_INVALID: return MAL_CHANNEL_NONE; + case MAL_PA_CHANNEL_POSITION_MONO: return MAL_CHANNEL_MONO; + case MAL_PA_CHANNEL_POSITION_FRONT_LEFT: return MAL_CHANNEL_FRONT_LEFT; + case MAL_PA_CHANNEL_POSITION_FRONT_RIGHT: return MAL_CHANNEL_FRONT_RIGHT; + case MAL_PA_CHANNEL_POSITION_FRONT_CENTER: return MAL_CHANNEL_FRONT_CENTER; + case MAL_PA_CHANNEL_POSITION_REAR_CENTER: return MAL_CHANNEL_BACK_CENTER; + case MAL_PA_CHANNEL_POSITION_REAR_LEFT: return MAL_CHANNEL_BACK_LEFT; + case MAL_PA_CHANNEL_POSITION_REAR_RIGHT: return MAL_CHANNEL_BACK_RIGHT; + case MAL_PA_CHANNEL_POSITION_LFE: return MAL_CHANNEL_LFE; + case MAL_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: return MAL_CHANNEL_FRONT_LEFT_CENTER; + case MAL_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: return MAL_CHANNEL_FRONT_RIGHT_CENTER; + case MAL_PA_CHANNEL_POSITION_SIDE_LEFT: return MAL_CHANNEL_SIDE_LEFT; + case MAL_PA_CHANNEL_POSITION_SIDE_RIGHT: return MAL_CHANNEL_SIDE_RIGHT; + case MAL_PA_CHANNEL_POSITION_AUX0: return MAL_CHANNEL_AUX_0; + case MAL_PA_CHANNEL_POSITION_AUX1: return MAL_CHANNEL_AUX_1; + case MAL_PA_CHANNEL_POSITION_AUX2: return MAL_CHANNEL_AUX_2; + case MAL_PA_CHANNEL_POSITION_AUX3: return MAL_CHANNEL_AUX_3; + case MAL_PA_CHANNEL_POSITION_AUX4: return MAL_CHANNEL_AUX_4; + case MAL_PA_CHANNEL_POSITION_AUX5: return MAL_CHANNEL_AUX_5; + case MAL_PA_CHANNEL_POSITION_AUX6: return MAL_CHANNEL_AUX_6; + case MAL_PA_CHANNEL_POSITION_AUX7: return MAL_CHANNEL_AUX_7; + case MAL_PA_CHANNEL_POSITION_AUX8: return MAL_CHANNEL_AUX_8; + case MAL_PA_CHANNEL_POSITION_AUX9: return MAL_CHANNEL_AUX_9; + case MAL_PA_CHANNEL_POSITION_AUX10: return MAL_CHANNEL_AUX_10; + case MAL_PA_CHANNEL_POSITION_AUX11: return MAL_CHANNEL_AUX_11; + case MAL_PA_CHANNEL_POSITION_AUX12: return MAL_CHANNEL_AUX_12; + case MAL_PA_CHANNEL_POSITION_AUX13: return MAL_CHANNEL_AUX_13; + case MAL_PA_CHANNEL_POSITION_AUX14: return MAL_CHANNEL_AUX_14; + case MAL_PA_CHANNEL_POSITION_AUX15: return MAL_CHANNEL_AUX_15; + case MAL_PA_CHANNEL_POSITION_AUX16: return MAL_CHANNEL_AUX_16; + case MAL_PA_CHANNEL_POSITION_AUX17: return MAL_CHANNEL_AUX_17; + case MAL_PA_CHANNEL_POSITION_AUX18: return MAL_CHANNEL_AUX_18; + case MAL_PA_CHANNEL_POSITION_AUX19: return MAL_CHANNEL_AUX_19; + case MAL_PA_CHANNEL_POSITION_AUX20: return MAL_CHANNEL_AUX_20; + case MAL_PA_CHANNEL_POSITION_AUX21: return MAL_CHANNEL_AUX_21; + case MAL_PA_CHANNEL_POSITION_AUX22: return MAL_CHANNEL_AUX_22; + case MAL_PA_CHANNEL_POSITION_AUX23: return MAL_CHANNEL_AUX_23; + case MAL_PA_CHANNEL_POSITION_AUX24: return MAL_CHANNEL_AUX_24; + case MAL_PA_CHANNEL_POSITION_AUX25: return MAL_CHANNEL_AUX_25; + case MAL_PA_CHANNEL_POSITION_AUX26: return MAL_CHANNEL_AUX_26; + case MAL_PA_CHANNEL_POSITION_AUX27: return MAL_CHANNEL_AUX_27; + case MAL_PA_CHANNEL_POSITION_AUX28: return MAL_CHANNEL_AUX_28; + case MAL_PA_CHANNEL_POSITION_AUX29: return MAL_CHANNEL_AUX_29; + case MAL_PA_CHANNEL_POSITION_AUX30: return MAL_CHANNEL_AUX_30; + case MAL_PA_CHANNEL_POSITION_AUX31: return MAL_CHANNEL_AUX_31; + case MAL_PA_CHANNEL_POSITION_TOP_CENTER: return MAL_CHANNEL_TOP_CENTER; + case MAL_PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return MAL_CHANNEL_TOP_FRONT_LEFT; + case MAL_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return MAL_CHANNEL_TOP_FRONT_RIGHT; + case MAL_PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return MAL_CHANNEL_TOP_FRONT_CENTER; + case MAL_PA_CHANNEL_POSITION_TOP_REAR_LEFT: return MAL_CHANNEL_TOP_BACK_LEFT; + case MAL_PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return MAL_CHANNEL_TOP_BACK_RIGHT; + case MAL_PA_CHANNEL_POSITION_TOP_REAR_CENTER: return MAL_CHANNEL_TOP_BACK_CENTER; + default: return MAL_CHANNEL_NONE; + } +} + +#if 0 +mal_pa_channel_position_t mal_channel_position_to_pulse(mal_channel position) +{ + switch (position) + { + case MAL_CHANNEL_NONE: return MAL_PA_CHANNEL_POSITION_INVALID; + case MAL_CHANNEL_FRONT_LEFT: return MAL_PA_CHANNEL_POSITION_FRONT_LEFT; + case MAL_CHANNEL_FRONT_RIGHT: return MAL_PA_CHANNEL_POSITION_FRONT_RIGHT; + case MAL_CHANNEL_FRONT_CENTER: return MAL_PA_CHANNEL_POSITION_FRONT_CENTER; + case MAL_CHANNEL_LFE: return MAL_PA_CHANNEL_POSITION_LFE; + case MAL_CHANNEL_BACK_LEFT: return MAL_PA_CHANNEL_POSITION_REAR_LEFT; + case MAL_CHANNEL_BACK_RIGHT: return MAL_PA_CHANNEL_POSITION_REAR_RIGHT; + case MAL_CHANNEL_FRONT_LEFT_CENTER: return MAL_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; + case MAL_CHANNEL_FRONT_RIGHT_CENTER: return MAL_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; + case MAL_CHANNEL_BACK_CENTER: return MAL_PA_CHANNEL_POSITION_REAR_CENTER; + case MAL_CHANNEL_SIDE_LEFT: return MAL_PA_CHANNEL_POSITION_SIDE_LEFT; + case MAL_CHANNEL_SIDE_RIGHT: return MAL_PA_CHANNEL_POSITION_SIDE_RIGHT; + case MAL_CHANNEL_TOP_CENTER: return MAL_PA_CHANNEL_POSITION_TOP_CENTER; + case MAL_CHANNEL_TOP_FRONT_LEFT: return MAL_PA_CHANNEL_POSITION_TOP_FRONT_LEFT; + case MAL_CHANNEL_TOP_FRONT_CENTER: return MAL_PA_CHANNEL_POSITION_TOP_FRONT_CENTER; + case MAL_CHANNEL_TOP_FRONT_RIGHT: return MAL_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; + case MAL_CHANNEL_TOP_BACK_LEFT: return MAL_PA_CHANNEL_POSITION_TOP_REAR_LEFT; + case MAL_CHANNEL_TOP_BACK_CENTER: return MAL_PA_CHANNEL_POSITION_TOP_REAR_CENTER; + case MAL_CHANNEL_TOP_BACK_RIGHT: return MAL_PA_CHANNEL_POSITION_TOP_REAR_RIGHT; + case MAL_CHANNEL_19: return MAL_PA_CHANNEL_POSITION_AUX18; + case MAL_CHANNEL_20: return MAL_PA_CHANNEL_POSITION_AUX19; + case MAL_CHANNEL_21: return MAL_PA_CHANNEL_POSITION_AUX20; + case MAL_CHANNEL_22: return MAL_PA_CHANNEL_POSITION_AUX21; + case MAL_CHANNEL_23: return MAL_PA_CHANNEL_POSITION_AUX22; + case MAL_CHANNEL_24: return MAL_PA_CHANNEL_POSITION_AUX23; + case MAL_CHANNEL_25: return MAL_PA_CHANNEL_POSITION_AUX24; + case MAL_CHANNEL_26: return MAL_PA_CHANNEL_POSITION_AUX25; + case MAL_CHANNEL_27: return MAL_PA_CHANNEL_POSITION_AUX26; + case MAL_CHANNEL_28: return MAL_PA_CHANNEL_POSITION_AUX27; + case MAL_CHANNEL_29: return MAL_PA_CHANNEL_POSITION_AUX28; + case MAL_CHANNEL_30: return MAL_PA_CHANNEL_POSITION_AUX29; + case MAL_CHANNEL_31: return MAL_PA_CHANNEL_POSITION_AUX30; + case MAL_CHANNEL_32: return MAL_PA_CHANNEL_POSITION_AUX31; + default: return (mal_pa_channel_position_t)position; + } +} +#endif + + +mal_result mal_wait_for_operation__pulse(mal_context* pContext, mal_pa_mainloop* pMainLoop, mal_pa_operation* pOP) +{ + mal_assert(pContext != NULL); + mal_assert(pMainLoop != NULL); + mal_assert(pOP != NULL); + + while (((mal_pa_operation_get_state_proc)pContext->pulse.pa_operation_get_state)(pOP) != MAL_PA_OPERATION_DONE) { + int error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pMainLoop, 1, NULL); + if (error < 0) { + return mal_result_from_pulse(error); + } + } + + return MAL_SUCCESS; +} + +mal_result mal_device__wait_for_operation__pulse(mal_device* pDevice, mal_pa_operation* pOP) +{ + mal_assert(pDevice != NULL); + mal_assert(pOP != NULL); + + return mal_wait_for_operation__pulse(pDevice->pContext, (mal_pa_mainloop*)pDevice->pulse.pMainLoop, pOP); +} + + +mal_bool32 mal_context_is_device_id_equal__pulse(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) +{ + mal_assert(pContext != NULL); + mal_assert(pID0 != NULL); + mal_assert(pID1 != NULL); + (void)pContext; + + return mal_strcmp(pID0->pulse, pID1->pulse) == 0; +} + + +typedef struct +{ + mal_context* pContext; + mal_enum_devices_callback_proc callback; + void* pUserData; + mal_bool32 isTerminated; +} mal_context_enumerate_devices_callback_data__pulse; + +void mal_context_enumerate_devices_sink_callback__pulse(mal_pa_context* pPulseContext, const mal_pa_sink_info* pSinkInfo, int endOfList, void* pUserData) +{ + mal_context_enumerate_devices_callback_data__pulse* pData = (mal_context_enumerate_devices_callback_data__pulse*)pUserData; + mal_assert(pData != NULL); + + if (endOfList || pData->isTerminated) { + return; + } + + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + + // The name from PulseAudio is the ID for mini_al. + if (pSinkInfo->name != NULL) { + mal_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSinkInfo->name, (size_t)-1); + } + + // The description from PulseAudio is the name for mini_al. + if (pSinkInfo->description != NULL) { + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSinkInfo->description, (size_t)-1); + } + + pData->isTerminated = !pData->callback(pData->pContext, mal_device_type_playback, &deviceInfo, pData->pUserData); +} + +void mal_context_enumerate_devices_source_callback__pulse(mal_pa_context* pPulseContext, const mal_pa_source_info* pSinkInfo, int endOfList, void* pUserData) +{ + mal_context_enumerate_devices_callback_data__pulse* pData = (mal_context_enumerate_devices_callback_data__pulse*)pUserData; + mal_assert(pData != NULL); + + if (endOfList || pData->isTerminated) { + return; + } + + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + + // The name from PulseAudio is the ID for mini_al. + if (pSinkInfo->name != NULL) { + mal_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSinkInfo->name, (size_t)-1); + } + + // The description from PulseAudio is the name for mini_al. + if (pSinkInfo->description != NULL) { + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSinkInfo->description, (size_t)-1); + } + + pData->isTerminated = !pData->callback(pData->pContext, mal_device_type_capture, &deviceInfo, pData->pUserData); +} + +mal_result mal_context_enumerate_devices__pulse(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +{ + mal_assert(pContext != NULL); + mal_assert(callback != NULL); + + mal_result result = MAL_SUCCESS; + + mal_context_enumerate_devices_callback_data__pulse callbackData; + callbackData.pContext = pContext; + callbackData.callback = callback; + callbackData.pUserData = pUserData; + callbackData.isTerminated = MAL_FALSE; + + mal_pa_operation* pOP = NULL; + + mal_pa_mainloop* pMainLoop = ((mal_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); + if (pMainLoop == NULL) { + return MAL_FAILED_TO_INIT_BACKEND; + } + + mal_pa_mainloop_api* pAPI = ((mal_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)(pMainLoop); + if (pAPI == NULL) { + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return MAL_FAILED_TO_INIT_BACKEND; + } + + mal_pa_context* pPulseContext = ((mal_pa_context_new_proc)pContext->pulse.pa_context_new)(pAPI, pContext->config.pulse.pApplicationName); + if (pPulseContext == NULL) { + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return MAL_FAILED_TO_INIT_BACKEND; + } + + int error = ((mal_pa_context_connect_proc)pContext->pulse.pa_context_connect)(pPulseContext, pContext->config.pulse.pServerName, 0, NULL); + if (error != MAL_PA_OK) { + ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return mal_result_from_pulse(error); + } + + while (((mal_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext) != MAL_PA_CONTEXT_READY) { + error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pMainLoop, 1, NULL); + if (error < 0) { + result = mal_result_from_pulse(error); + goto done; + } + } + + + // Playback. + if (!callbackData.isTerminated) { + pOP = ((mal_pa_context_get_sink_info_list_proc)pContext->pulse.pa_context_get_sink_info_list)(pPulseContext, mal_context_enumerate_devices_sink_callback__pulse, &callbackData); + if (pOP == NULL) { + result = MAL_ERROR; + goto done; + } + + result = mal_wait_for_operation__pulse(pContext, pMainLoop, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + if (result != MAL_SUCCESS) { + goto done; + } + } + + + // Capture. + if (!callbackData.isTerminated) { + pOP = ((mal_pa_context_get_source_info_list_proc)pContext->pulse.pa_context_get_source_info_list)(pPulseContext, mal_context_enumerate_devices_source_callback__pulse, &callbackData); + if (pOP == NULL) { + result = MAL_ERROR; + goto done; + } + + result = mal_wait_for_operation__pulse(pContext, pMainLoop, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + if (result != MAL_SUCCESS) { + goto done; + } + } + +done: + ((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)(pPulseContext); + ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return result; +} + + +typedef struct +{ + mal_device_info* pDeviceInfo; + mal_bool32 foundDevice; +} mal_context_get_device_info_callback_data__pulse; + +void mal_context_get_device_info_sink_callback__pulse(mal_pa_context* pPulseContext, const mal_pa_sink_info* pInfo, int endOfList, void* pUserData) +{ + if (endOfList > 0) { + return; + } + + mal_context_get_device_info_callback_data__pulse* pData = (mal_context_get_device_info_callback_data__pulse*)pUserData; + mal_assert(pData != NULL); + pData->foundDevice = MAL_TRUE; + + if (pInfo->name != NULL) { + mal_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); + } + + if (pInfo->description != NULL) { + mal_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); + } +} + +void mal_context_get_device_info_source_callback__pulse(mal_pa_context* pPulseContext, const mal_pa_source_info* pInfo, int endOfList, void* pUserData) +{ + if (endOfList > 0) { + return; + } + + mal_context_get_device_info_callback_data__pulse* pData = (mal_context_get_device_info_callback_data__pulse*)pUserData; + mal_assert(pData != NULL); + pData->foundDevice = MAL_TRUE; + + if (pInfo->name != NULL) { + mal_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); + } + + if (pInfo->description != NULL) { + mal_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); + } +} + +mal_result mal_context_get_device_info__pulse(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +{ + mal_assert(pContext != NULL); + (void)shareMode; + + mal_result result = MAL_SUCCESS; + + mal_context_get_device_info_callback_data__pulse callbackData; + callbackData.pDeviceInfo = pDeviceInfo; + callbackData.foundDevice = MAL_FALSE; + + mal_pa_operation* pOP = NULL; + + mal_pa_mainloop* pMainLoop = ((mal_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); + if (pMainLoop == NULL) { + return MAL_FAILED_TO_INIT_BACKEND; + } + + mal_pa_mainloop_api* pAPI = ((mal_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)(pMainLoop); + if (pAPI == NULL) { + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return MAL_FAILED_TO_INIT_BACKEND; + } + + mal_pa_context* pPulseContext = ((mal_pa_context_new_proc)pContext->pulse.pa_context_new)(pAPI, pContext->config.pulse.pApplicationName); + if (pPulseContext == NULL) { + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return MAL_FAILED_TO_INIT_BACKEND; + } + + int error = ((mal_pa_context_connect_proc)pContext->pulse.pa_context_connect)(pPulseContext, pContext->config.pulse.pServerName, 0, NULL); + if (error != MAL_PA_OK) { + ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return mal_result_from_pulse(error); + } + + while (((mal_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext) != MAL_PA_CONTEXT_READY) { + error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pMainLoop, 1, NULL); + if (error < 0) { + result = mal_result_from_pulse(error); + goto done; + } + } + + if (deviceType == mal_device_type_playback) { + pOP = ((mal_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)(pPulseContext, pDeviceID->pulse, mal_context_get_device_info_sink_callback__pulse, &callbackData); + } else { + pOP = ((mal_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)(pPulseContext, pDeviceID->pulse, mal_context_get_device_info_source_callback__pulse, &callbackData); + } + + if (pOP != NULL) { + mal_wait_for_operation__pulse(pContext, pMainLoop, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + } else { + result = MAL_ERROR; + goto done; + } + + if (!callbackData.foundDevice) { + result = MAL_NO_DEVICE; + goto done; + } + + +done: + ((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)(pPulseContext); + ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + return result; +} + + +mal_result mal_context_init__pulse(mal_context* pContext) +{ + mal_assert(pContext != NULL); + +#ifndef MAL_NO_RUNTIME_LINKING + // libpulse.so + const char* libpulseNames[] = { + "libpulse.so", + "libpulse.so.0" + }; + + for (size_t i = 0; i < mal_countof(libpulseNames); ++i) { + pContext->pulse.pulseSO = mal_dlopen(libpulseNames[i]); + if (pContext->pulse.pulseSO != NULL) { + break; + } + } + + if (pContext->pulse.pulseSO == NULL) { + return MAL_NO_BACKEND; + } + + pContext->pulse.pa_mainloop_new = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_mainloop_new"); + pContext->pulse.pa_mainloop_free = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_mainloop_free"); + pContext->pulse.pa_mainloop_get_api = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_mainloop_get_api"); + pContext->pulse.pa_mainloop_iterate = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_mainloop_iterate"); + pContext->pulse.pa_mainloop_wakeup = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_mainloop_wakeup"); + pContext->pulse.pa_context_new = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_new"); + pContext->pulse.pa_context_unref = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_unref"); + pContext->pulse.pa_context_connect = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_connect"); + pContext->pulse.pa_context_disconnect = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_disconnect"); + pContext->pulse.pa_context_set_state_callback = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_set_state_callback"); + pContext->pulse.pa_context_get_state = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_get_state"); + pContext->pulse.pa_context_get_sink_info_list = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_get_sink_info_list"); + pContext->pulse.pa_context_get_source_info_list = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_get_source_info_list"); + pContext->pulse.pa_context_get_sink_info_by_name = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_get_sink_info_by_name"); + pContext->pulse.pa_context_get_source_info_by_name = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_context_get_source_info_by_name"); + pContext->pulse.pa_operation_unref = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_operation_unref"); + pContext->pulse.pa_operation_get_state = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_operation_get_state"); + pContext->pulse.pa_channel_map_init_extend = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_channel_map_init_extend"); + pContext->pulse.pa_channel_map_valid = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_channel_map_valid"); + pContext->pulse.pa_channel_map_compatible = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_channel_map_compatible"); + pContext->pulse.pa_stream_new = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_new"); + pContext->pulse.pa_stream_unref = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_unref"); + pContext->pulse.pa_stream_connect_playback = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_connect_playback"); + pContext->pulse.pa_stream_connect_record = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_connect_record"); + pContext->pulse.pa_stream_disconnect = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_disconnect"); + pContext->pulse.pa_stream_get_state = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_state"); + 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_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"); + pContext->pulse.pa_stream_flush = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_flush"); + pContext->pulse.pa_stream_drain = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_drain"); + pContext->pulse.pa_stream_cork = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_cork"); + pContext->pulse.pa_stream_trigger = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_trigger"); + pContext->pulse.pa_stream_begin_write = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_begin_write"); + pContext->pulse.pa_stream_write = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_write"); + pContext->pulse.pa_stream_peek = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_peek"); + pContext->pulse.pa_stream_drop = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_drop"); +#else + // This strange assignment system is just for type safety. + mal_pa_mainloop_new_proc _pa_mainloop_new = pa_mainloop_new; + mal_pa_mainloop_free_proc _pa_mainloop_free = pa_mainloop_free; + mal_pa_mainloop_get_api_proc _pa_mainloop_get_api = pa_mainloop_get_api; + mal_pa_mainloop_iterate_proc _pa_mainloop_iterate = pa_mainloop_iterate; + mal_pa_mainloop_wakeup_proc _pa_mainloop_wakeup = pa_mainloop_wakeup; + mal_pa_context_new_proc _pa_context_new = pa_context_new; + mal_pa_context_unref_proc _pa_context_unref = pa_context_unref; + mal_pa_context_connect_proc _pa_context_connect = pa_context_connect; + mal_pa_context_disconnect_proc _pa_context_disconnect = pa_context_disconnect; + mal_pa_context_set_state_callback_proc _pa_context_set_state_callback = pa_context_set_state_callback; + mal_pa_context_get_state_proc _pa_context_get_state = pa_context_get_state; + mal_pa_context_get_sink_info_list_proc _pa_context_get_sink_info_list = pa_context_get_sink_info_list; + mal_pa_context_get_source_info_list_proc _pa_context_get_source_info_list = pa_context_get_source_info_list; + mal_pa_context_get_sink_info_by_name_proc _pa_context_get_sink_info_by_name = pa_context_get_sink_info_by_name; + mal_pa_context_get_source_info_by_name_proc _pa_context_get_source_info_by_name= pa_context_get_source_info_by_name; + mal_pa_operation_unref_proc _pa_operation_unref = pa_operation_unref; + mal_pa_operation_get_state_proc _pa_operation_get_state = pa_operation_get_state; + mal_pa_channel_map_init_extend_proc _pa_channel_map_init_extend = pa_channel_map_init_extend; + mal_pa_channel_map_valid_proc _pa_channel_map_valid = pa_channel_map_valid; + mal_pa_channel_map_compatible_proc _pa_channel_map_compatible = pa_channel_map_compatible; + mal_pa_stream_new_proc _pa_stream_new = pa_stream_new; + mal_pa_stream_unref_proc _pa_stream_unref = pa_stream_unref; + mal_pa_stream_connect_playback_proc _pa_stream_connect_playback = pa_stream_connect_playback; + mal_pa_stream_connect_record_proc _pa_stream_connect_record = pa_stream_connect_record; + mal_pa_stream_disconnect_proc _pa_stream_disconnect = pa_stream_disconnect; + mal_pa_stream_get_state_proc _pa_stream_get_state = pa_stream_get_state; + 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_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; + mal_pa_stream_flush_proc _pa_stream_flush = pa_stream_flush; + mal_pa_stream_drain_proc _pa_stream_drain = pa_stream_drain; + mal_pa_stream_cork_proc _pa_stream_cork = pa_stream_cork; + mal_pa_stream_trigger_proc _pa_stream_trigger = pa_stream_trigger; + mal_pa_stream_begin_write_proc _pa_stream_begin_write = pa_stream_begin_write; + mal_pa_stream_write_proc _pa_stream_write = pa_stream_write; + mal_pa_stream_peek_proc _pa_stream_peek = pa_stream_peek; + mal_pa_stream_drop_proc _pa_stream_drop = pa_stream_drop; + + pContext->pulse.pa_mainloop_new = (mal_proc)_pa_mainloop_new; + pContext->pulse.pa_mainloop_free = (mal_proc)_pa_mainloop_free; + pContext->pulse.pa_mainloop_get_api = (mal_proc)_pa_mainloop_get_api; + pContext->pulse.pa_mainloop_iterate = (mal_proc)_pa_mainloop_iterate; + pContext->pulse.pa_mainloop_wakeup = (mal_proc)_pa_mainloop_wakeup; + pContext->pulse.pa_context_new = (mal_proc)_pa_context_new; + pContext->pulse.pa_context_unref = (mal_proc)_pa_context_unref; + pContext->pulse.pa_context_connect = (mal_proc)_pa_context_connect; + pContext->pulse.pa_context_disconnect = (mal_proc)_pa_context_disconnect; + pContext->pulse.pa_context_set_state_callback = (mal_proc)_pa_context_set_state_callback; + pContext->pulse.pa_context_get_state = (mal_proc)_pa_context_get_state; + pContext->pulse.pa_context_get_sink_info_list = (mal_proc)_pa_context_get_sink_info_list; + pContext->pulse.pa_context_get_source_info_list = (mal_proc)_pa_context_get_source_info_list; + pContext->pulse.pa_context_get_sink_info_by_name = (mal_proc)_pa_context_get_sink_info_by_name; + pContext->pulse.pa_context_get_source_info_by_name = (mal_proc)_pa_context_get_source_info_by_name; + pContext->pulse.pa_operation_unref = (mal_proc)_pa_operation_unref; + pContext->pulse.pa_operation_get_state = (mal_proc)_pa_operation_get_state; + pContext->pulse.pa_channel_map_init_extend = (mal_proc)_pa_channel_map_init_extend; + pContext->pulse.pa_channel_map_valid = (mal_proc)_pa_channel_map_valid; + pContext->pulse.pa_channel_map_compatible = (mal_proc)_pa_channel_map_compatible; + pContext->pulse.pa_stream_new = (mal_proc)_pa_stream_new; + pContext->pulse.pa_stream_unref = (mal_proc)_pa_stream_unref; + pContext->pulse.pa_stream_connect_playback = (mal_proc)_pa_stream_connect_playback; + pContext->pulse.pa_stream_connect_record = (mal_proc)_pa_stream_connect_record; + pContext->pulse.pa_stream_disconnect = (mal_proc)_pa_stream_disconnect; + pContext->pulse.pa_stream_get_state = (mal_proc)_pa_stream_get_state; + 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_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; + pContext->pulse.pa_stream_flush = (mal_proc)_pa_stream_flush; + pContext->pulse.pa_stream_drain = (mal_proc)_pa_stream_drain; + pContext->pulse.pa_stream_cork = (mal_proc)_pa_stream_cork; + pContext->pulse.pa_stream_trigger = (mal_proc)_pa_stream_trigger; + pContext->pulse.pa_stream_begin_write = (mal_proc)_pa_stream_begin_write; + pContext->pulse.pa_stream_write = (mal_proc)_pa_stream_write; + pContext->pulse.pa_stream_peek = (mal_proc)_pa_stream_peek; + pContext->pulse.pa_stream_drop = (mal_proc)_pa_stream_drop; +#endif + + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__pulse; + pContext->onEnumDevices = mal_context_enumerate_devices__pulse; + pContext->onGetDeviceInfo = mal_context_get_device_info__pulse; + + return MAL_SUCCESS; +} + +mal_result mal_context_uninit__pulse(mal_context* pContext) +{ + mal_assert(pContext != NULL); + mal_assert(pContext->backend == mal_backend_pulseaudio); + +#ifndef MAL_NO_RUNTIME_LINKING + mal_dlclose(pContext->pulse.pulseSO); +#endif + + return MAL_SUCCESS; +} + + +void mal_pulse_device_state_callback(mal_pa_context* pPulseContext, void* pUserData) +{ + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + + pDevice->pulse.pulseContextState = ((mal_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext); +} + +void mal_pulse_device_write_callback(mal_pa_stream* pStream, size_t sizeInBytes, void* pUserData) +{ + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + + size_t bytesRemaining = sizeInBytes; + while (bytesRemaining > 0) { + size_t bytesToReadFromClient = bytesRemaining; + if (bytesToReadFromClient > 0xFFFFFFFF) { + bytesToReadFromClient = 0xFFFFFFFF; + } + + void* pBuffer = NULL; + int error = ((mal_pa_stream_begin_write_proc)pContext->pulse.pa_stream_begin_write)((mal_pa_stream*)pDevice->pulse.pStream, &pBuffer, &bytesToReadFromClient); + if (error < 0) { + mal_post_error(pDevice, "[PulseAudio] Failed to retrieve write buffer for sending data to the device.", mal_result_from_pulse(error)); + return; + } + + 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); + + 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, "[PulseAudio] Failed to write data to the PulseAudio stream.", mal_result_from_pulse(error)); + return; + } + } + + bytesRemaining -= bytesToReadFromClient; + } + } +} + +void mal_pulse_device_read_callback(mal_pa_stream* pStream, size_t sizeInBytes, void* pUserData) +{ + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + + size_t bytesRemaining = sizeInBytes; + while (bytesRemaining > 0) { + size_t bytesToSendToClient = bytesRemaining; + if (bytesToSendToClient > 0xFFFFFFFF) { + bytesToSendToClient = 0xFFFFFFFF; + } + + const void* pBuffer = NULL; + int error = ((mal_pa_stream_peek_proc)pContext->pulse.pa_stream_peek)((mal_pa_stream*)pDevice->pulse.pStream, &pBuffer, &sizeInBytes); + if (error < 0) { + mal_post_error(pDevice, "[PulseAudio] Failed to retrieve read buffer for reading data from the device.", mal_result_from_pulse(error)); + return; + } + + if (pBuffer != NULL) { + mal_uint32 framesToSendToClient = (mal_uint32)bytesToSendToClient / (pDevice->internalChannels*mal_get_bytes_per_sample(pDevice->internalFormat)); + if (framesToSendToClient > 0) { + mal_device__send_frames_to_client(pDevice, framesToSendToClient, pBuffer); + } + } + + error = ((mal_pa_stream_drop_proc)pContext->pulse.pa_stream_drop)((mal_pa_stream*)pDevice->pulse.pStream); + if (error < 0) { + mal_post_error(pDevice, "[PulseAudio] Failed to drop fragment from the PulseAudio stream.", mal_result_from_pulse(error)); + } + + bytesRemaining -= bytesToSendToClient; + } +} + +void mal_device_sink_info_callback(mal_pa_context* pPulseContext, const mal_pa_sink_info* pInfo, int endOfList, void* pUserData) +{ + if (endOfList > 0) { + return; + } + + mal_pa_sink_info* pInfoOut = (mal_pa_sink_info*)pUserData; + mal_assert(pInfoOut != NULL); + + *pInfoOut = *pInfo; +} + +void mal_device_source_info_callback(mal_pa_context* pPulseContext, const mal_pa_source_info* pInfo, int endOfList, void* pUserData) +{ + if (endOfList > 0) { + return; + } + + mal_pa_source_info* pInfoOut = (mal_pa_source_info*)pUserData; + mal_assert(pInfoOut != NULL); + + *pInfoOut = *pInfo; +} + +void mal_device_sink_name_callback(mal_pa_context* pPulseContext, const mal_pa_sink_info* pInfo, int endOfList, void* pUserData) +{ + if (endOfList > 0) { + return; + } + + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + mal_strncpy_s(pDevice->name, sizeof(pDevice->name), pInfo->description, (size_t)-1); +} + +void mal_device_source_name_callback(mal_pa_context* pPulseContext, const mal_pa_source_info* pInfo, int endOfList, void* pUserData) +{ + if (endOfList > 0) { + return; + } + + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + mal_strncpy_s(pDevice->name, sizeof(pDevice->name), pInfo->description, (size_t)-1); +} + +void mal_device_uninit__pulse(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + + ((mal_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((mal_pa_stream*)pDevice->pulse.pStream); + ((mal_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((mal_pa_stream*)pDevice->pulse.pStream); + ((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((mal_pa_context*)pDevice->pulse.pPulseContext); + ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)((mal_pa_context*)pDevice->pulse.pPulseContext); + ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((mal_pa_mainloop*)pDevice->pulse.pMainLoop); +} + +mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +{ + (void)pContext; + + mal_assert(pDevice != NULL); + mal_zero_object(&pDevice->pulse); + + mal_result result = MAL_SUCCESS; + int error = 0; + + const char* dev = NULL; + if (pDeviceID != NULL) { + dev = pDeviceID->pulse; + } + + mal_pa_sink_info sinkInfo; + mal_pa_source_info sourceInfo; + mal_pa_operation* pOP = NULL; + + mal_pa_sample_spec ss; + mal_pa_channel_map cmap; + mal_pa_buffer_attr attr; + + const mal_pa_sample_spec* pActualSS = NULL; + const mal_pa_channel_map* pActualCMap = NULL; + const mal_pa_buffer_attr* pActualAttr = NULL; + + + + pDevice->pulse.pMainLoop = ((mal_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); + if (pDevice->pulse.pMainLoop == NULL) { + result = mal_post_error(pDevice, "[PulseAudio] Failed to create main loop for device.", MAL_FAILED_TO_INIT_BACKEND); + goto on_error0; + } + + pDevice->pulse.pAPI = ((mal_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((mal_pa_mainloop*)pDevice->pulse.pMainLoop); + if (pDevice->pulse.pAPI == NULL) { + result = mal_post_error(pDevice, "[PulseAudio] Failed to retrieve PulseAudio main loop.", MAL_FAILED_TO_INIT_BACKEND); + goto on_error1; + } + + pDevice->pulse.pPulseContext = ((mal_pa_context_new_proc)pContext->pulse.pa_context_new)((mal_pa_mainloop_api*)pDevice->pulse.pAPI, pContext->config.pulse.pApplicationName); + if (pDevice->pulse.pPulseContext == NULL) { + result = mal_post_error(pDevice, "[PulseAudio] Failed to create PulseAudio context for device.", MAL_FAILED_TO_INIT_BACKEND); + goto on_error1; + } + + error = ((mal_pa_context_connect_proc)pContext->pulse.pa_context_connect)((mal_pa_context*)pDevice->pulse.pPulseContext, pContext->config.pulse.pServerName, (pContext->config.pulse.tryAutoSpawn) ? 0 : MAL_PA_CONTEXT_NOAUTOSPAWN, NULL); + if (error != MAL_PA_OK) { + result = mal_post_error(pDevice, "[PulseAudio] Failed to connect PulseAudio context.", mal_result_from_pulse(error)); + goto on_error2; + } + + + pDevice->pulse.pulseContextState = MAL_PA_CONTEXT_UNCONNECTED; + ((mal_pa_context_set_state_callback_proc)pContext->pulse.pa_context_set_state_callback)((mal_pa_context*)pDevice->pulse.pPulseContext, mal_pulse_device_state_callback, pDevice); + + // Wait for PulseAudio to get itself ready before returning. + for (;;) { + if (pDevice->pulse.pulseContextState == MAL_PA_CONTEXT_READY) { + break; + } else { + error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((mal_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); // 1 = block. + if (error < 0) { + result = mal_post_error(pDevice, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio context.", mal_result_from_pulse(error)); + goto on_error3; + } + continue; + } + + // An error may have occurred. + if (pDevice->pulse.pulseContextState == MAL_PA_CONTEXT_FAILED || pDevice->pulse.pulseContextState == MAL_PA_CONTEXT_TERMINATED) { + result = mal_post_error(pDevice, "[PulseAudio] An error occurred while connecting the PulseAudio context.", MAL_ERROR); + goto on_error3; + } + + error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((mal_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); + if (error < 0) { + result = mal_post_error(pDevice, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio context.", mal_result_from_pulse(error)); + goto on_error3; + } + } + + + if (type == mal_device_type_playback) { + pOP = ((mal_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, dev, mal_device_sink_info_callback, &sinkInfo); + } else { + pOP = ((mal_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, dev, mal_device_source_info_callback, &sourceInfo); + } + + if (pOP != NULL) { + mal_device__wait_for_operation__pulse(pDevice, pOP); + ((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; + } else { + ss = sourceInfo.sample_spec; + cmap = sourceInfo.channel_map; + } +#endif + + attr.maxlength = pConfig->bufferSizeInFrames * mal_get_bytes_per_sample(mal_format_from_pulse(ss.format))*ss.channels; + attr.tlength = attr.maxlength / pConfig->periods; + attr.prebuf = (mal_uint32)-1; + attr.minreq = attr.tlength; + attr.fragsize = attr.tlength; + + char streamName[256]; + if (pConfig->pulse.pStreamName != NULL) { + mal_strncpy_s(streamName, sizeof(streamName), pConfig->pulse.pStreamName, (size_t)-1); + } else { + static int g_StreamCounter = 0; + mal_strcpy_s(streamName, sizeof(streamName), "mini_al:"); + mal_itoa_s(g_StreamCounter, streamName + 8, sizeof(streamName)-8, 10); // 8 = strlen("mini_al:") + g_StreamCounter += 1; + } + + pDevice->pulse.pStream = ((mal_pa_stream_new_proc)pContext->pulse.pa_stream_new)((mal_pa_context*)pDevice->pulse.pPulseContext, streamName, &ss, &cmap); + if (pDevice->pulse.pStream == NULL) { + result = mal_post_error(pDevice, "[PulseAudio] Failed to create PulseAudio stream.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + goto on_error3; + } + + + + 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); + } 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); + } + + if (error != MAL_PA_OK) { + result = mal_post_error(pDevice, "[PulseAudio] Failed to connect PulseAudio stream.", mal_result_from_pulse(error)); + goto on_error4; + } + + while (((mal_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((mal_pa_stream*)pDevice->pulse.pStream) != MAL_PA_STREAM_READY) { + error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((mal_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); + if (error < 0) { + result = mal_post_error(pDevice, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio stream.", mal_result_from_pulse(error)); + goto on_error5; + } + } + + + // 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) { + ss = *pActualSS; + } + + pDevice->internalFormat = mal_format_from_pulse(ss.format); + pDevice->internalChannels = ss.channels; + pDevice->internalSampleRate = ss.rate; + + + // Internal channel map. + pActualCMap = ((mal_pa_stream_get_channel_map_proc)pContext->pulse.pa_stream_get_channel_map)((mal_pa_stream*)pDevice->pulse.pStream); + if (pActualCMap != NULL) { + cmap = *pActualCMap; + } + + for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) { + pDevice->internalChannelMap[iChannel] = mal_channel_position_from_pulse(cmap.map[iChannel]); + } + + + // Buffer size. + pActualAttr = ((mal_pa_stream_get_buffer_attr_proc)pContext->pulse.pa_stream_get_buffer_attr)((mal_pa_stream*)pDevice->pulse.pStream); + if (pActualAttr != NULL) { + attr = *pActualAttr; + } + + pDevice->bufferSizeInFrames = attr.maxlength / (mal_get_bytes_per_sample(pDevice->internalFormat)*pDevice->internalChannels); + pDevice->periods = attr.maxlength / attr.tlength; + + + // 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); + if (dev != NULL) { + mal_pa_operation* pOP = NULL; + if (type == mal_device_type_playback) { + pOP = ((mal_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, dev, mal_device_sink_name_callback, pDevice); + } else { + pOP = ((mal_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, dev, mal_device_source_name_callback, pDevice); + } + + if (pOP != NULL) { + mal_device__wait_for_operation__pulse(pDevice, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + } + } + + + // Set callbacks for reading and writing data to/from the PulseAudio stream. + if (type == mal_device_type_playback) { + ((mal_pa_stream_set_write_callback_proc)pContext->pulse.pa_stream_set_write_callback)((mal_pa_stream*)pDevice->pulse.pStream, mal_pulse_device_write_callback, pDevice); + } else { + ((mal_pa_stream_set_read_callback_proc)pContext->pulse.pa_stream_set_read_callback)((mal_pa_stream*)pDevice->pulse.pStream, mal_pulse_device_read_callback, pDevice); + } + + + pDevice->pulse.fragmentSizeInBytes = attr.tlength; + + return MAL_SUCCESS; + + +on_error5: ((mal_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((mal_pa_stream*)pDevice->pulse.pStream); +on_error4: ((mal_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((mal_pa_stream*)pDevice->pulse.pStream); +on_error3: ((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((mal_pa_context*)pDevice->pulse.pPulseContext); +on_error2: ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)((mal_pa_context*)pDevice->pulse.pPulseContext); +on_error1: ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((mal_pa_mainloop*)pDevice->pulse.pMainLoop); +on_error0: + return result; +} + + +void mal_pulse_operation_complete_callback(mal_pa_stream* pStream, int success, void* pUserData) +{ + mal_bool32* pIsSuccessful = (mal_bool32*)pUserData; + mal_assert(pIsSuccessful != NULL); + + *pIsSuccessful = (mal_bool32)success; +} + +mal_result mal_device__cork_stream__pulse(mal_device* pDevice, int cork) +{ + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + + mal_bool32 wasSuccessful = MAL_FALSE; + mal_pa_operation* pOP = ((mal_pa_stream_cork_proc)pContext->pulse.pa_stream_cork)((mal_pa_stream*)pDevice->pulse.pStream, cork, mal_pulse_operation_complete_callback, &wasSuccessful); + if (pOP == NULL) { + return mal_post_error(pDevice, "[PulseAudio] Failed to cork PulseAudio stream.", (cork == 0) ? MAL_FAILED_TO_START_BACKEND_DEVICE : MAL_FAILED_TO_STOP_BACKEND_DEVICE); + } + + mal_result result = mal_device__wait_for_operation__pulse(pDevice, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + + if (result != MAL_SUCCESS) { + return mal_post_error(pDevice, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to cork.", result); + } + + if (!wasSuccessful) { + if (cork) { + return mal_post_error(pDevice, "[PulseAudio] Failed to stop PulseAudio stream.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); + } else { + return mal_post_error(pDevice, "[PulseAudio] Failed to start PulseAudio stream.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } + } + + return MAL_SUCCESS; +} + +mal_result mal_device__start_backend__pulse(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + + // For both playback and capture we need to uncork the stream. Afterwards, for playback we need to fill in an initial chunk + // of data, equal to the trigger length. That should then start actual playback. + mal_result result = mal_device__cork_stream__pulse(pDevice, 0); + if (result != MAL_SUCCESS) { + return result; + } + + // A playback device is started by simply writing data to it. For capture we do nothing. + if (pDevice->type == mal_device_type_playback) { + // Playback. + mal_pulse_device_write_callback((mal_pa_stream*)pDevice->pulse.pStream, pDevice->pulse.fragmentSizeInBytes, pDevice); + + // Force an immediate start of the device just to be sure. + mal_pa_operation* pOP = ((mal_pa_stream_trigger_proc)pContext->pulse.pa_stream_trigger)((mal_pa_stream*)pDevice->pulse.pStream, NULL, NULL); + if (pOP != NULL) { + mal_device__wait_for_operation__pulse(pDevice, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + } + } else { + // Capture. Do nothing. + } + + return MAL_SUCCESS; +} + +mal_result mal_device__stop_backend__pulse(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + + mal_result result = mal_device__cork_stream__pulse(pDevice, 1); + if (result != MAL_SUCCESS) { + return result; + } + + // For playback, buffers need to be flushed. For capture they need to be drained. + mal_bool32 wasSuccessful; + mal_pa_operation* pOP = NULL; + if (pDevice->type == mal_device_type_playback) { + pOP = ((mal_pa_stream_flush_proc)pContext->pulse.pa_stream_flush)((mal_pa_stream*)pDevice->pulse.pStream, mal_pulse_operation_complete_callback, &wasSuccessful); + } else { + pOP = ((mal_pa_stream_drain_proc)pContext->pulse.pa_stream_drain)((mal_pa_stream*)pDevice->pulse.pStream, mal_pulse_operation_complete_callback, &wasSuccessful); + } + + if (pOP == NULL) { + return mal_post_error(pDevice, "[PulseAudio] Failed to flush buffers after stopping PulseAudio stream.", MAL_ERROR); + } + + result = mal_device__wait_for_operation__pulse(pDevice, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + + if (result != MAL_SUCCESS) { + return mal_post_error(pDevice, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to flush.", result); + } + + if (!wasSuccessful) { + return mal_post_error(pDevice, "[PulseAudio] Failed to flush buffers after stopping PulseAudio stream.", MAL_ERROR); + } + + return MAL_SUCCESS; +} + +mal_result mal_device__break_main_loop__pulse(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + + pDevice->pulse.breakFromMainLoop = MAL_TRUE; + ((mal_pa_mainloop_wakeup_proc)pContext->pulse.pa_mainloop_wakeup)((mal_pa_mainloop*)pDevice->pulse.pMainLoop); + + return MAL_SUCCESS; +} + +mal_result mal_device__main_loop__pulse(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + + pDevice->pulse.breakFromMainLoop = MAL_FALSE; + while (!pDevice->pulse.breakFromMainLoop) { + // Break from the main loop if the device isn't started anymore. Likely what's happened is the application + // has requested that the device be stopped. + if (!mal_device_is_started(pDevice)) { + break; + } + + int resultPA = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((mal_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); + if (resultPA < 0) { + break; // Some error occurred. + } + } + + return MAL_SUCCESS; +} +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// +// JACK Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_HAS_JACK + +// It is assumed jack.h is available when compile-time linking is being used. +#ifdef MAL_NO_RUNTIME_LINKING +#include + +typedef jack_nframes_t mal_jack_nframes_t; +typedef jack_options_t mal_jack_options_t; +typedef jack_status_t mal_jack_status_t; +typedef jack_client_t mal_jack_client_t; +typedef jack_port_t mal_jack_port_t; +typedef JackProcessCallback mal_JackProcessCallback; +typedef JackBufferSizeCallback mal_JackBufferSizeCallback; +typedef JackShutdownCallback mal_JackShutdownCallback; +#define MAL_JACK_DEFAULT_AUDIO_TYPE JACK_DEFAULT_AUDIO_TYPE +#define mal_JackNoStartServer JackNoStartServer +#define mal_JackPortIsInput JackPortIsInput +#define mal_JackPortIsOutput JackPortIsOutput +#define mal_JackPortIsPhysical JackPortIsPhysical +#else +typedef mal_uint32 mal_jack_nframes_t; +typedef int mal_jack_options_t; +typedef int mal_jack_status_t; +typedef struct mal_jack_client_t mal_jack_client_t; +typedef struct mal_jack_port_t mal_jack_port_t; +typedef int (* mal_JackProcessCallback) (mal_jack_nframes_t nframes, void* arg); +typedef int (* mal_JackBufferSizeCallback)(mal_jack_nframes_t nframes, void* arg); +typedef void (* mal_JackShutdownCallback) (void* arg); +#define MAL_JACK_DEFAULT_AUDIO_TYPE "32 bit float mono audio" +#define mal_JackNoStartServer 1 +#define mal_JackPortIsInput 1 +#define mal_JackPortIsOutput 2 +#define mal_JackPortIsPhysical 4 +#endif + +typedef mal_jack_client_t* (* mal_jack_client_open_proc) (const char* client_name, mal_jack_options_t options, mal_jack_status_t* status, ...); +typedef int (* mal_jack_client_close_proc) (mal_jack_client_t* client); +typedef int (* mal_jack_client_name_size_proc) (); +typedef int (* mal_jack_set_process_callback_proc) (mal_jack_client_t* client, mal_JackProcessCallback process_callback, void* arg); +typedef int (* mal_jack_set_buffer_size_callback_proc)(mal_jack_client_t* client, mal_JackBufferSizeCallback bufsize_callback, void* arg); +typedef void (* mal_jack_on_shutdown_proc) (mal_jack_client_t* client, mal_JackShutdownCallback function, void* arg); +typedef mal_jack_nframes_t (* mal_jack_get_sample_rate_proc) (mal_jack_client_t* client); +typedef mal_jack_nframes_t (* mal_jack_get_buffer_size_proc) (mal_jack_client_t* client); +typedef const char** (* mal_jack_get_ports_proc) (mal_jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags); +typedef int (* mal_jack_activate_proc) (mal_jack_client_t* client); +typedef int (* mal_jack_deactivate_proc) (mal_jack_client_t* client); +typedef int (* mal_jack_connect_proc) (mal_jack_client_t* client, const char* source_port, const char* destination_port); +typedef mal_jack_port_t* (* mal_jack_port_register_proc) (mal_jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size); +typedef const char* (* mal_jack_port_name_proc) (const mal_jack_port_t* port); +typedef void* (* mal_jack_port_get_buffer_proc) (mal_jack_port_t* port, mal_jack_nframes_t nframes); +typedef void (* mal_jack_free_proc) (void* ptr); + +mal_bool32 mal_context_is_device_id_equal__jack(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) +{ + mal_assert(pContext != NULL); + mal_assert(pID0 != NULL); + mal_assert(pID1 != NULL); + (void)pContext; + + return pID0->jack == pID1->jack; +} + +mal_result mal_context_enumerate_devices__jack(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +{ + mal_assert(pContext != NULL); + mal_assert(callback != NULL); + + mal_bool32 cbResult = MAL_TRUE; + + // Playback. + if (cbResult) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); + } + + // Capture. + if (cbResult) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); + } + + return MAL_SUCCESS; +} + +mal_result mal_context_get_device_info__jack(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +{ + mal_assert(pContext != NULL); + + (void)pContext; + (void)shareMode; + + if (pDeviceID != NULL && pDeviceID->jack != 0) { + return MAL_NO_DEVICE; // Don't know the device. + } + + // Name / Description + if (deviceType == mal_device_type_playback) { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } + + return MAL_SUCCESS; +} + +mal_result mal_context_init__jack(mal_context* pContext) +{ + mal_assert(pContext != NULL); + +#ifndef MAL_NO_RUNTIME_LINKING + // libjack.so + const char* libjackNames[] = { +#ifdef MAL_WIN32 + "libjack.dll" +#else + "libjack.so", + "libjack.so.0" +#endif + }; + + for (size_t i = 0; i < mal_countof(libjackNames); ++i) { + pContext->jack.jackSO = mal_dlopen(libjackNames[i]); + if (pContext->jack.jackSO != NULL) { + break; + } + } + + if (pContext->jack.jackSO == NULL) { + return MAL_NO_BACKEND; + } + + pContext->jack.jack_client_open = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_client_open"); + pContext->jack.jack_client_close = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_client_close"); + pContext->jack.jack_client_name_size = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_client_name_size"); + pContext->jack.jack_set_process_callback = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_set_process_callback"); + pContext->jack.jack_set_buffer_size_callback = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_set_buffer_size_callback"); + pContext->jack.jack_on_shutdown = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_on_shutdown"); + pContext->jack.jack_get_sample_rate = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_get_sample_rate"); + pContext->jack.jack_get_buffer_size = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_get_buffer_size"); + pContext->jack.jack_get_ports = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_get_ports"); + pContext->jack.jack_activate = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_activate"); + pContext->jack.jack_deactivate = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_deactivate"); + pContext->jack.jack_connect = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_connect"); + pContext->jack.jack_port_register = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_port_register"); + pContext->jack.jack_port_name = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_port_name"); + pContext->jack.jack_port_get_buffer = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_port_get_buffer"); + pContext->jack.jack_free = (mal_proc)mal_dlsym(pContext->jack.jackSO, "jack_free"); +#else + // This strange assignment system is here just to ensure type safety of mini_al's function pointer + // types. If anything differs slightly the compiler should throw a warning. + mal_jack_client_open_proc _jack_client_open = jack_client_open; + mal_jack_client_close_proc _jack_client_close = jack_client_close; + mal_jack_client_name_size_proc _jack_client_name_size = jack_client_name_size; + mal_jack_set_process_callback_proc _jack_set_process_callback = jack_set_process_callback; + mal_jack_set_buffer_size_callback_proc _jack_set_buffer_size_callback = jack_set_buffer_size_callback; + mal_jack_on_shutdown_proc _jack_on_shutdown = jack_on_shutdown; + mal_jack_get_sample_rate_proc _jack_get_sample_rate = jack_get_sample_rate; + mal_jack_get_buffer_size_proc _jack_get_buffer_size = jack_get_buffer_size; + mal_jack_get_ports_proc _jack_get_ports = jack_get_ports; + mal_jack_activate_proc _jack_activate = jack_activate; + mal_jack_deactivate_proc _jack_deactivate = jack_deactivate; + mal_jack_connect_proc _jack_connect = jack_connect; + mal_jack_port_register_proc _jack_port_register = jack_port_register; + mal_jack_port_name_proc _jack_port_name = jack_port_name; + mal_jack_port_get_buffer_proc _jack_port_get_buffer = jack_port_get_buffer; + mal_jack_free_proc _jack_free = jack_free; + + pContext->jack.jack_client_open = (mal_proc)_jack_client_open; + pContext->jack.jack_client_close = (mal_proc)_jack_client_close; + pContext->jack.jack_client_name_size = (mal_proc)_jack_client_name_size; + pContext->jack.jack_set_process_callback = (mal_proc)_jack_set_process_callback; + pContext->jack.jack_set_buffer_size_callback = (mal_proc)_jack_set_buffer_size_callback; + pContext->jack.jack_on_shutdown = (mal_proc)_jack_on_shutdown; + pContext->jack.jack_get_sample_rate = (mal_proc)_jack_get_sample_rate; + pContext->jack.jack_get_buffer_size = (mal_proc)_jack_get_buffer_size; + pContext->jack.jack_get_ports = (mal_proc)_jack_get_ports; + pContext->jack.jack_activate = (mal_proc)_jack_activate; + pContext->jack.jack_deactivate = (mal_proc)_jack_deactivate; + pContext->jack.jack_connect = (mal_proc)_jack_connect; + pContext->jack.jack_port_register = (mal_proc)_jack_port_register; + pContext->jack.jack_port_name = (mal_proc)_jack_port_name; + pContext->jack.jack_port_get_buffer = (mal_proc)_jack_port_get_buffer; + pContext->jack.jack_free = (mal_proc)_jack_free; +#endif + + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__jack; + pContext->onEnumDevices = mal_context_enumerate_devices__jack; + pContext->onGetDeviceInfo = mal_context_get_device_info__jack; + + return MAL_SUCCESS; +} + +mal_result mal_context_uninit__jack(mal_context* pContext) +{ + mal_assert(pContext != NULL); + mal_assert(pContext->backend == mal_backend_jack); + +#ifndef MAL_NO_RUNTIME_LINKING + mal_dlclose(pContext->jack.jackSO); +#endif + + return MAL_SUCCESS; +} + +void mal_device_uninit__jack(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + + if (pDevice->jack.pClient != NULL) { + ((mal_jack_client_close_proc)pContext->jack.jack_client_close)((mal_jack_client_t*)pDevice->jack.pClient); + } +} + +void mal_device__jack_shutdown_callback(void* pUserData) +{ + // JACK died. Stop the device. + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + mal_device_stop(pDevice); +} + +int mal_device__jack_buffer_size_callback(mal_jack_nframes_t frameCount, void* pUserData) +{ + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + float* pNewBuffer = (float*)mal_realloc(pDevice->jack.pIntermediaryBuffer, frameCount * (pDevice->internalChannels*mal_get_bytes_per_sample(pDevice->internalFormat))); + if (pNewBuffer == NULL) { + return MAL_OUT_OF_MEMORY; + } + + pDevice->jack.pIntermediaryBuffer = pNewBuffer; + pDevice->bufferSizeInFrames = frameCount * pDevice->periods; + + return 0; +} + +int mal_device__jack_process_callback(mal_jack_nframes_t frameCount, void* pUserData) +{ + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + + if (pDevice->type == mal_device_type_playback) { + mal_device__read_frames_from_client(pDevice, frameCount, pDevice->jack.pIntermediaryBuffer); + + // Channels need to be deinterleaved. + for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) { + float* pDst = (float*)((mal_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((mal_jack_port_t*)pDevice->jack.pPorts[iChannel], frameCount); + if (pDst != NULL) { + const float* pSrc = pDevice->jack.pIntermediaryBuffer + iChannel; + for (mal_jack_nframes_t iFrame = 0; iFrame < frameCount; ++iFrame) { + *pDst = *pSrc; + + pDst += 1; + pSrc += pDevice->internalChannels; + } + } + } + } else { + // Channels need to be interleaved. + for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) { + const float* pSrc = (const float*)((mal_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((mal_jack_port_t*)pDevice->jack.pPorts[iChannel], frameCount); + if (pSrc != NULL) { + float* pDst = pDevice->jack.pIntermediaryBuffer + iChannel; + for (mal_jack_nframes_t iFrame = 0; iFrame < frameCount; ++iFrame) { + *pDst = *pSrc; + + pDst += pDevice->internalChannels; + pSrc += 1; + } + } + } + + mal_device__send_frames_to_client(pDevice, frameCount, pDevice->jack.pIntermediaryBuffer); + } + + return 0; +} + +mal_result mal_device_init__jack(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +{ + mal_assert(pContext != NULL); + mal_assert(pConfig != NULL); + mal_assert(pDevice != NULL); + + (void)pContext; + (void)pConfig; + + // Only supporting default devices with JACK. + if (pDeviceID != NULL && pDeviceID->jack != 0) { + return MAL_NO_DEVICE; + } + + + // Open the client. + size_t maxClientNameSize = ((mal_jack_client_name_size_proc)pContext->jack.jack_client_name_size)(); // Includes null terminator. + + char clientName[256]; + mal_strncpy_s(clientName, mal_min(sizeof(clientName), maxClientNameSize), (pContext->config.jack.pClientName != NULL) ? pContext->config.jack.pClientName : "mini_al", (size_t)-1); + + mal_jack_status_t status; + pDevice->jack.pClient = ((mal_jack_client_open_proc)pContext->jack.jack_client_open)(clientName, (pContext->config.jack.tryStartServer) ? 0 : mal_JackNoStartServer, &status, NULL); + if (pDevice->jack.pClient == NULL) { + return mal_post_error(pDevice, "[JACK] Failed to open client.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + // Callbacks. + if (((mal_jack_set_process_callback_proc)pContext->jack.jack_set_process_callback)((mal_jack_client_t*)pDevice->jack.pClient, mal_device__jack_process_callback, pDevice) != 0) { + return mal_post_error(pDevice, "[JACK] Failed to set process callback.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + if (((mal_jack_set_buffer_size_callback_proc)pContext->jack.jack_set_buffer_size_callback)((mal_jack_client_t*)pDevice->jack.pClient, mal_device__jack_buffer_size_callback, pDevice) != 0) { + return mal_post_error(pDevice, "[JACK] Failed to set buffer size callback.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + ((mal_jack_on_shutdown_proc)pContext->jack.jack_on_shutdown)((mal_jack_client_t*)pDevice->jack.pClient, mal_device__jack_shutdown_callback, pDevice); + + + // The format is always f32. + pDevice->internalFormat = mal_format_f32; + + // A port is a channel. + unsigned long serverPortFlags; + unsigned long clientPortFlags; + if (type == mal_device_type_playback) { + serverPortFlags = mal_JackPortIsInput; + clientPortFlags = mal_JackPortIsOutput; + } else { + serverPortFlags = mal_JackPortIsOutput; + clientPortFlags = mal_JackPortIsInput; + } + + const char** ppPorts = ((mal_jack_get_ports_proc)pContext->jack.jack_get_ports)((mal_jack_client_t*)pDevice->jack.pClient, NULL, NULL, mal_JackPortIsPhysical | serverPortFlags); + if (ppPorts == NULL) { + return mal_post_error(pDevice, "[JACK] Failed to query physical ports.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + pDevice->internalChannels = 0; + while (ppPorts[pDevice->internalChannels] != NULL) { + char name[64]; + if (type == mal_device_type_playback) { + mal_strcpy_s(name, sizeof(name), "playback"); + mal_itoa_s((int)pDevice->internalChannels, name+8, sizeof(name)-8, 10); // 8 = length of "playback" + } else { + mal_strcpy_s(name, sizeof(name), "capture"); + mal_itoa_s((int)pDevice->internalChannels, name+7, sizeof(name)-7, 10); // 7 = length of "capture" + } + + pDevice->jack.pPorts[pDevice->internalChannels] = ((mal_jack_port_register_proc)pContext->jack.jack_port_register)((mal_jack_client_t*)pDevice->jack.pClient, name, MAL_JACK_DEFAULT_AUDIO_TYPE, clientPortFlags, 0); + if (pDevice->jack.pPorts[pDevice->internalChannels] == NULL) { + ((mal_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); + mal_device_uninit__jack(pDevice); + return mal_post_error(pDevice, "[JACK] Failed to register ports.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + pDevice->internalChannels += 1; + } + + ((mal_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); + ppPorts = NULL; + + // We set the sample rate here, but apparently this can change. This is incompatible with mini_al, so changing sample rates will not be supported. + pDevice->internalSampleRate = ((mal_jack_get_sample_rate_proc)pContext->jack.jack_get_sample_rate)((mal_jack_client_t*)pDevice->jack.pClient); + + // I don't think the channel map can be queried, so just use defaults for now. + mal_get_standard_channel_map(mal_standard_channel_map_alsa, pDevice->internalChannels, pDevice->internalChannelMap); + + // The buffer size in frames can change. + pDevice->periods = 2; + pDevice->bufferSizeInFrames = ((mal_jack_get_buffer_size_proc)pContext->jack.jack_get_buffer_size)((mal_jack_client_t*)pDevice->jack.pClient) * pDevice->periods; + + // Initial allocation for the intermediary buffer. + pDevice->jack.pIntermediaryBuffer = (float*)mal_malloc((pDevice->bufferSizeInFrames/pDevice->periods)*(pDevice->internalChannels*mal_get_bytes_per_sample(pDevice->internalFormat))); + if (pDevice->jack.pIntermediaryBuffer == NULL) { + mal_device_uninit__jack(pDevice); + return MAL_OUT_OF_MEMORY; + } + + return MAL_SUCCESS; +} + + +mal_result mal_device__start_backend__jack(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + + int resultJACK = ((mal_jack_activate_proc)pContext->jack.jack_activate)((mal_jack_client_t*)pDevice->jack.pClient); + if (resultJACK != 0) { + return mal_post_error(pDevice, "[JACK] Failed to activate the JACK client.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } + + const char** ppServerPorts; + if (pDevice->type == mal_device_type_playback) { + ppServerPorts = ((mal_jack_get_ports_proc)pContext->jack.jack_get_ports)((mal_jack_client_t*)pDevice->jack.pClient, NULL, NULL, mal_JackPortIsPhysical | mal_JackPortIsInput); + } else { + ppServerPorts = ((mal_jack_get_ports_proc)pContext->jack.jack_get_ports)((mal_jack_client_t*)pDevice->jack.pClient, NULL, NULL, mal_JackPortIsPhysical | mal_JackPortIsOutput); + } + + if (ppServerPorts == NULL) { + ((mal_jack_deactivate_proc)pContext->jack.jack_deactivate)((mal_jack_client_t*)pDevice->jack.pClient); + return mal_post_error(pDevice, "[JACK] Failed to retrieve physical ports.", MAL_ERROR); + } + + for (size_t i = 0; ppServerPorts[i] != NULL; ++i) { + const char* pServerPort = ppServerPorts[i]; + mal_assert(pServerPort != NULL); + + const char* pClientPort = ((mal_jack_port_name_proc)pContext->jack.jack_port_name)((mal_jack_port_t*)pDevice->jack.pPorts[i]); + mal_assert(pClientPort != NULL); + + if (pDevice->type == mal_device_type_playback) { + resultJACK = ((mal_jack_connect_proc)pContext->jack.jack_connect)((mal_jack_client_t*)pDevice->jack.pClient, pClientPort, pServerPort); + } else { + resultJACK = ((mal_jack_connect_proc)pContext->jack.jack_connect)((mal_jack_client_t*)pDevice->jack.pClient, pServerPort, pClientPort); + } + + if (resultJACK != 0) { + ((mal_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); + ((mal_jack_deactivate_proc)pContext->jack.jack_deactivate)((mal_jack_client_t*)pDevice->jack.pClient); + return mal_post_error(pDevice, "[JACK] Failed to connect ports.", MAL_ERROR); + } + } + + ((mal_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); + + return MAL_SUCCESS; +} + +mal_result mal_device__stop_backend__jack(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_context* pContext = pDevice->pContext; + mal_assert(pContext != NULL); + + if (((mal_jack_deactivate_proc)pContext->jack.jack_deactivate)((mal_jack_client_t*)pDevice->jack.pClient) != 0) { + return mal_post_error(pDevice, "[JACK] An error occurred when deactivating the JACK client.", MAL_ERROR); + } + + return MAL_SUCCESS; +} +#endif + + + /////////////////////////////////////////////////////////////////////////////// // // OSS Backend @@ -6750,6 +11384,144 @@ int mal_open_temp_device__oss() return -1; } +mal_bool32 mal_context_is_device_id_equal__oss(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) +{ + mal_assert(pContext != NULL); + mal_assert(pID0 != NULL); + mal_assert(pID1 != NULL); + (void)pContext; + + return mal_strcmp(pID0->oss, pID1->oss) == 0; +} + +mal_result mal_context_enumerate_devices__oss(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +{ + mal_assert(pContext != NULL); + mal_assert(callback != NULL); + + int fd = mal_open_temp_device__oss(); + if (fd == -1) { + return mal_context_post_error(pContext, NULL, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration.", MAL_NO_BACKEND); + } + + oss_sysinfo si; + int result = ioctl(fd, SNDCTL_SYSINFO, &si); + if (result != -1) { + for (int iAudioDevice = 0; iAudioDevice < si.numaudios; ++iAudioDevice) { + oss_audioinfo ai; + ai.dev = iAudioDevice; + result = ioctl(fd, SNDCTL_AUDIOINFO, &ai); + if (result != -1) { + if (ai.devnode[0] != '\0') { // <-- Can be blank, according to documentation. + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + + // ID + mal_strncpy_s(deviceInfo.id.oss, sizeof(deviceInfo.id.oss), ai.devnode, (size_t)-1); + + // The human readable device name should be in the "ai.handle" variable, but it can + // sometimes be empty in which case we just fall back to "ai.name" which is less user + // friendly, but usually has a value. + if (ai.handle[0] != '\0') { + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ai.handle, (size_t)-1); + } else { + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ai.name, (size_t)-1); + } + + // The device can be both playback and capture. + mal_bool32 isTerminating = MAL_FALSE; + if (!isTerminating && (ai.caps & PCM_CAP_OUTPUT) != 0) { + isTerminating = !callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); + } + if (!isTerminating && (ai.caps & PCM_CAP_INPUT) != 0) { + isTerminating = !callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); + } + + if (isTerminating) { + break; + } + } + } + } + } else { + close(fd); + return mal_context_post_error(pContext, NULL, "[OSS] Failed to retrieve system information for device enumeration.", MAL_NO_BACKEND); + } + + close(fd); + return MAL_SUCCESS; +} + +mal_result mal_context_get_device_info__oss(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +{ + mal_assert(pContext != NULL); + (void)shareMode; + + // Handle the default device a little differently. + if (pDeviceID == NULL) { + if (deviceType == mal_device_type_playback) { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } + + return MAL_SUCCESS; + } + + + // If we get here it means we are _not_ using the default device. + mal_bool32 foundDevice = MAL_FALSE; + + int fd = mal_open_temp_device__oss(); + if (fd == -1) { + return mal_context_post_error(pContext, NULL, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration.", MAL_NO_BACKEND); + } + + oss_sysinfo si; + int result = ioctl(fd, SNDCTL_SYSINFO, &si); + if (result != -1) { + for (int iAudioDevice = 0; iAudioDevice < si.numaudios; ++iAudioDevice) { + oss_audioinfo ai; + ai.dev = iAudioDevice; + result = ioctl(fd, SNDCTL_AUDIOINFO, &ai); + if (result != -1) { + if (mal_strcmp(ai.devnode, pDeviceID->oss) == 0) { + // It has the same name, so now just confirm the type. + if ((deviceType == mal_device_type_playback && ((ai.caps & PCM_CAP_OUTPUT) != 0)) || + (deviceType == mal_device_type_capture && ((ai.caps & PCM_CAP_INPUT) != 0))) { + // ID + mal_strncpy_s(pDeviceInfo->id.oss, sizeof(pDeviceInfo->id.oss), ai.devnode, (size_t)-1); + + // The human readable device name should be in the "ai.handle" variable, but it can + // sometimes be empty in which case we just fall back to "ai.name" which is less user + // friendly, but usually has a value. + if (ai.handle[0] != '\0') { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), ai.handle, (size_t)-1); + } else { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), ai.name, (size_t)-1); + } + + foundDevice = MAL_TRUE; + break; + } + } + } + } + } else { + close(fd); + return mal_context_post_error(pContext, NULL, "[OSS] Failed to retrieve system information for device enumeration.", MAL_NO_BACKEND); + } + + + close(fd); + + if (foundDevice) { + return MAL_SUCCESS; + } else { + return MAL_NO_DEVICE; + } +} + mal_result mal_context_init__oss(mal_context* pContext) { mal_assert(pContext != NULL); @@ -6771,6 +11543,10 @@ mal_result mal_context_init__oss(mal_context* pContext) pContext->oss.versionMajor = ((ossVersion & 0xFF0000) >> 16); pContext->oss.versionMinor = ((ossVersion & 0x00FF00) >> 8); + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__oss; + pContext->onEnumDevices = mal_context_enumerate_devices__oss; + pContext->onGetDeviceInfo = mal_context_get_device_info__oss; + close(fd); return MAL_SUCCESS; } @@ -6784,83 +11560,7 @@ mal_result mal_context_uninit__oss(mal_context* pContext) return MAL_SUCCESS; } -static mal_result mal_enumerate_devices__oss(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) -{ - (void)pContext; - - mal_uint32 infoSize = *pCount; - *pCount = 0; - - // The object returned by SNDCTL_SYSINFO will have the information we're after. - int fd = mal_open_temp_device__oss(); - if (fd == -1) { - return mal_context_post_error(pContext, NULL, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration.", MAL_NO_BACKEND); - } - - oss_sysinfo si; - int result = ioctl(fd, SNDCTL_SYSINFO, &si); - if (result != -1) { - for (int iAudioDevice = 0; iAudioDevice < si.numaudios; ++iAudioDevice) { - oss_audioinfo ai; - ai.dev = iAudioDevice; - result = ioctl(fd, SNDCTL_AUDIOINFO, &ai); - if (result != -1) { - mal_bool32 includeThisDevice = MAL_FALSE; - if (type == mal_device_type_playback && (ai.caps & PCM_CAP_OUTPUT) != 0) { - includeThisDevice = MAL_TRUE; - } else if (type == mal_device_type_capture && (ai.caps & PCM_CAP_INPUT) != 0) { - includeThisDevice = MAL_TRUE; - } - - if (includeThisDevice) { - if (ai.devnode[0] != '\0') { // <-- Can be blank, according to documentation. - if (pInfo != NULL) { - if (infoSize > 0) { - mal_strncpy_s(pInfo->id.oss, sizeof(pInfo->id.oss), ai.devnode, (size_t)-1); - - // The human readable device name should be in the "ai.handle" variable, but it can - // sometimes be empty in which case we just fall back to "ai.name" which is less user - // friendly, but usually has a value. - if (ai.handle[0] != '\0') { - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), ai.handle, (size_t)-1); - } else { - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), ai.name, (size_t)-1); - } - - pInfo += 1; - infoSize -= 1; - *pCount += 1; - } - } else { - *pCount += 1; - } - } - } - } - } - } else { - // Failed to retrieve the system information. Just return a default device for both playback and capture. - if (pInfo != NULL) { - if (infoSize > 0) { - mal_strncpy_s(pInfo[0].id.oss, sizeof(pInfo[0].id.oss), "/dev/dsp", (size_t)-1); - if (type == mal_device_type_playback) { - mal_strncpy_s(pInfo[0].name, sizeof(pInfo[0].name), "Default Playback Device", (size_t)-1); - } else { - mal_strncpy_s(pInfo[0].name, sizeof(pInfo[0].name), "Default Capture Device", (size_t)-1); - } - - *pCount = 1; - } - } else { - *pCount = 1; - } - } - - close(fd); - return MAL_SUCCESS; -} - -static void mal_device_uninit__oss(mal_device* pDevice) +void mal_device_uninit__oss(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -6868,7 +11568,7 @@ static void mal_device_uninit__oss(mal_device* pDevice) mal_free(pDevice->oss.pIntermediaryBuffer); } -static mal_result mal_device_init__oss(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +mal_result mal_device_init__oss(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) { (void)pContext; @@ -6935,7 +11635,7 @@ static mal_result mal_device_init__oss(mal_context* pContext, mal_device_type ty return mal_post_error(pDevice, "[OSS] Failed to set sample rate.", MAL_FORMAT_NOT_SUPPORTED); } - pDevice->sampleRate = ossSampleRate; + pDevice->internalSampleRate = ossSampleRate; @@ -6944,7 +11644,7 @@ static mal_result mal_device_init__oss(mal_context* pContext, mal_device_type ty // // OSS wants the fragment size in bytes and a power of 2. When setting, we specify the power, not the actual // value. - mal_uint32 fragmentSizeInBytes = mal_round_to_power_of_2(pDevice->bufferSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat)); + mal_uint32 fragmentSizeInBytes = mal_round_to_power_of_2(pDevice->bufferSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat)); if (fragmentSizeInBytes < 16) { fragmentSizeInBytes = 16; } @@ -6963,14 +11663,13 @@ static mal_result mal_device_init__oss(mal_context* pContext, mal_device_type ty } int actualFragmentSizeInBytes = 1 << (ossFragment & 0xFFFF); - pDevice->oss.fragmentSizeInFrames = actualFragmentSizeInBytes / mal_get_sample_size_in_bytes(pDevice->internalFormat) / pDevice->internalChannels; + pDevice->oss.fragmentSizeInFrames = actualFragmentSizeInBytes / mal_get_bytes_per_sample(pDevice->internalFormat) / pDevice->internalChannels; pDevice->periods = (mal_uint32)(ossFragment >> 16); pDevice->bufferSizeInFrames = (mal_uint32)(pDevice->oss.fragmentSizeInFrames * pDevice->periods); - // Set the internal channel map. Not sure if this can be queried. For now just using our default assumptions. - mal_get_default_channel_mapping(pDevice->pContext->backend, pDevice->internalChannels, pDevice->internalChannelMap); + mal_get_standard_channel_map(mal_standard_channel_map_default, pDevice->internalChannels, pDevice->internalChannelMap); // When not using MMAP mode, we need to use an intermediary buffer for the client <-> device transfer. We do @@ -6985,7 +11684,7 @@ static mal_result mal_device_init__oss(mal_context* pContext, mal_device_type ty } -static mal_result mal_device__start_backend__oss(mal_device* pDevice) +mal_result mal_device__start_backend__oss(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -6997,7 +11696,7 @@ static mal_result mal_device__start_backend__oss(mal_device* pDevice) // Playback. mal_device__read_frames_from_client(pDevice, pDevice->oss.fragmentSizeInFrames, pDevice->oss.pIntermediaryBuffer); - int bytesWritten = write(pDevice->oss.fd, pDevice->oss.pIntermediaryBuffer, pDevice->oss.fragmentSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat)); + int bytesWritten = write(pDevice->oss.fd, pDevice->oss.pIntermediaryBuffer, pDevice->oss.fragmentSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat)); if (bytesWritten == -1) { return mal_post_error(pDevice, "[OSS] Failed to send initial chunk of data to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); } @@ -7008,7 +11707,7 @@ static mal_result mal_device__start_backend__oss(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__stop_backend__oss(mal_device* pDevice) +mal_result mal_device__stop_backend__oss(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -7031,7 +11730,7 @@ static mal_result mal_device__stop_backend__oss(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__break_main_loop__oss(mal_device* pDevice) +mal_result mal_device__break_main_loop__oss(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -7039,7 +11738,7 @@ static mal_result mal_device__break_main_loop__oss(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__main_loop__oss(mal_device* pDevice) +mal_result mal_device__main_loop__oss(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -7055,18 +11754,18 @@ static mal_result mal_device__main_loop__oss(mal_device* pDevice) // Playback. mal_device__read_frames_from_client(pDevice, pDevice->oss.fragmentSizeInFrames, pDevice->oss.pIntermediaryBuffer); - int bytesWritten = write(pDevice->oss.fd, pDevice->oss.pIntermediaryBuffer, pDevice->oss.fragmentSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat)); + int bytesWritten = write(pDevice->oss.fd, pDevice->oss.pIntermediaryBuffer, pDevice->oss.fragmentSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat)); if (bytesWritten < 0) { return mal_post_error(pDevice, "[OSS] Failed to send data from the client to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); } } else { // Capture. - int bytesRead = read(pDevice->oss.fd, pDevice->oss.pIntermediaryBuffer, pDevice->oss.fragmentSizeInFrames * mal_get_sample_size_in_bytes(pDevice->internalFormat)); + int bytesRead = read(pDevice->oss.fd, pDevice->oss.pIntermediaryBuffer, pDevice->oss.fragmentSizeInFrames * mal_get_bytes_per_sample(pDevice->internalFormat)); if (bytesRead < 0) { return mal_post_error(pDevice, "[OSS] Failed to read data from the device to be sent to the client.", MAL_FAILED_TO_READ_DATA_FROM_DEVICE); } - mal_uint32 framesRead = (mal_uint32)bytesRead / pDevice->internalChannels / mal_get_sample_size_in_bytes(pDevice->internalFormat); + mal_uint32 framesRead = (mal_uint32)bytesRead / pDevice->internalChannels / mal_get_bytes_per_sample(pDevice->internalFormat); mal_device__send_frames_to_client(pDevice, framesRead, pDevice->oss.pIntermediaryBuffer); } } @@ -7087,8 +11786,24 @@ static mal_result mal_device__main_loop__oss(mal_device* pDevice) #include #endif +// OpenSL|ES has one-per-application objects :( +SLObjectItf g_malEngineObjectSL = NULL; +SLEngineItf g_malEngineSL = NULL; +mal_uint32 g_malOpenSLInitCounter = 0; + +#define MAL_OPENSL_OBJ(p) (*((SLObjectItf)(p))) +#define MAL_OPENSL_OUTPUTMIX(p) (*((SLOutputMixItf)(p))) +#define MAL_OPENSL_PLAY(p) (*((SLPlayItf)(p))) +#define MAL_OPENSL_RECORD(p) (*((SLRecordItf)(p))) + +#ifdef MAL_ANDROID +#define MAL_OPENSL_BUFFERQUEUE(p) (*((SLAndroidSimpleBufferQueueItf)(p))) +#else +#define MAL_OPENSL_BUFFERQUEUE(p) (*((SLBufferQueueItf)(p))) +#endif + // Converts an individual OpenSL-style channel identifier (SL_SPEAKER_FRONT_LEFT, etc.) to mini_al. -static mal_uint8 mal_channel_id_to_mal__opensl(SLuint32 id) +mal_uint8 mal_channel_id_to_mal__opensl(SLuint32 id) { switch (id) { @@ -7115,10 +11830,11 @@ static mal_uint8 mal_channel_id_to_mal__opensl(SLuint32 id) } // Converts an individual mini_al channel identifier (MAL_CHANNEL_FRONT_LEFT, etc.) to OpenSL-style. -static SLuint32 mal_channel_id_to_opensl(mal_uint8 id) +SLuint32 mal_channel_id_to_opensl(mal_uint8 id) { switch (id) { + case MAL_CHANNEL_MONO: return SL_SPEAKER_FRONT_CENTER; case MAL_CHANNEL_FRONT_LEFT: return SL_SPEAKER_FRONT_LEFT; case MAL_CHANNEL_FRONT_RIGHT: return SL_SPEAKER_FRONT_RIGHT; case MAL_CHANNEL_FRONT_CENTER: return SL_SPEAKER_FRONT_CENTER; @@ -7142,7 +11858,7 @@ static SLuint32 mal_channel_id_to_opensl(mal_uint8 id) } // Converts a channel mapping to an OpenSL-style channel mask. -static SLuint32 mal_channel_map_to_channel_mask__opensl(const mal_uint8 channelMap[MAL_MAX_CHANNELS], mal_uint32 channels) +SLuint32 mal_channel_map_to_channel_mask__opensl(const mal_channel channelMap[MAL_MAX_CHANNELS], mal_uint32 channels) { SLuint32 channelMask = 0; for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { @@ -7153,20 +11869,26 @@ static SLuint32 mal_channel_map_to_channel_mask__opensl(const mal_uint8 channelM } // Converts an OpenSL-style channel mask to a mini_al channel map. -static void mal_channel_mask_to_channel_map__opensl(SLuint32 channelMask, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS]) +void mal_channel_mask_to_channel_map__opensl(SLuint32 channelMask, mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]) { - if (channels == 2 && channelMask == 0) { + if (channels == 1 && channelMask == 0) { + channelMap[0] = MAL_CHANNEL_MONO; + } else if (channels == 2 && channelMask == 0) { channelMap[0] = MAL_CHANNEL_FRONT_LEFT; channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; } else { - // Just iterate over each bit. - mal_uint32 iChannel = 0; - for (mal_uint32 iBit = 0; iBit < 32; ++iBit) { - SLuint32 bitValue = (channelMask & (1 << iBit)); - if (bitValue != 0) { - // The bit is set. - channelMap[iChannel] = mal_channel_id_to_mal__opensl(bitValue); - iChannel += 1; + if (channels == 1 && (channelMask & SL_SPEAKER_FRONT_CENTER) != 0) { + channelMap[0] = MAL_CHANNEL_MONO; + } else { + // Just iterate over each bit. + mal_uint32 iChannel = 0; + for (mal_uint32 iBit = 0; iBit < 32; ++iBit) { + SLuint32 bitValue = (channelMask & (1UL << iBit)); + if (bitValue != 0) { + // The bit is set. + channelMap[iChannel] = mal_channel_id_to_mal__opensl(bitValue); + iChannel += 1; + } } } } @@ -7221,11 +11943,199 @@ SLuint32 mal_round_to_standard_sample_rate__opensl(SLuint32 samplesPerSec) return SL_SAMPLINGRATE_16; } + +mal_bool32 mal_context_is_device_id_equal__opensl(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) +{ + mal_assert(pContext != NULL); + mal_assert(pID0 != NULL); + mal_assert(pID1 != NULL); + (void)pContext; + + return pID0->opensl == pID1->opensl; +} + +mal_result mal_context_enumerate_devices__opensl(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +{ + mal_assert(pContext != NULL); + mal_assert(callback != NULL); + + // TODO: Test Me. + // + // This is currently untested, so for now we are just returning default devices. +#if 0 && !defined(MAL_ANDROID) + mal_bool32 isTerminated = MAL_FALSE; + + SLuint32 pDeviceIDs[128]; + SLint32 deviceCount = sizeof(pDeviceIDs) / sizeof(pDeviceIDs[0]); + + SLAudioIODeviceCapabilitiesItf deviceCaps; + SLresult resultSL = (*g_malEngineObjectSL)->GetInterface(g_malEngineObjectSL, SL_IID_AUDIOIODEVICECAPABILITIES, &deviceCaps); + if (resultSL != SL_RESULT_SUCCESS) { + // The interface may not be supported so just report a default device. + goto return_default_device; + } + + // Playback + if (!isTerminated) { + resultSL = (*deviceCaps)->GetAvailableAudioOutputs(deviceCaps, &deviceCount, pDeviceIDs); + if (resultSL != SL_RESULT_SUCCESS) { + return MAL_NO_DEVICE; + } + + for (SLint32 iDevice = 0; iDevice < deviceCount; ++iDevice) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + deviceInfo.id.opensl = pDeviceIDs[iDevice]; + + SLAudioOutputDescriptor desc; + resultSL = (*deviceCaps)->QueryAudioOutputCapabilities(deviceCaps, deviceInfo.id.opensl, &desc); + if (resultSL == SL_RESULT_SUCCESS) { + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), (const char*)desc.pDeviceName, (size_t)-1); + + mal_bool32 cbResult = callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); + if (cbResult == MAL_FALSE) { + isTerminated = MAL_TRUE; + break; + } + } + } + } + + // Capture + if (!isTerminated) { + resultSL = (*deviceCaps)->GetAvailableAudioInputs(deviceCaps, &deviceCount, pDeviceIDs); + if (resultSL != SL_RESULT_SUCCESS) { + return MAL_NO_DEVICE; + } + + for (SLint32 iDevice = 0; iDevice < deviceCount; ++iDevice) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + deviceInfo.id.opensl = pDeviceIDs[iDevice]; + + SLAudioInputDescriptor desc; + resultSL = (*deviceCaps)->QueryAudioInputCapabilities(deviceCaps, deviceInfo.id.opensl, &desc); + if (resultSL == SL_RESULT_SUCCESS) { + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), (const char*)desc.deviceName, (size_t)-1); + + mal_bool32 cbResult = callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); + if (cbResult == MAL_FALSE) { + isTerminated = MAL_TRUE; + break; + } + } + } + } + + return MAL_SUCCESS; +#else + goto return_default_device; +#endif + +return_default_device: + mal_bool32 cbResult = MAL_TRUE; + + // Playback. + if (cbResult) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); + } + + // Capture. + if (cbResult) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); + } + + return MAL_SUCCESS; +} + +mal_result mal_context_get_device_info__opensl(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +{ + mal_assert(pContext != NULL); + (void)shareMode; + + // TODO: Test Me. + // + // This is currently untested, so for now we are just returning default devices. +#if 0 && !defined(MAL_ANDROID) + SLAudioIODeviceCapabilitiesItf deviceCaps; + SLresult resultSL = (*g_malEngineObjectSL)->GetInterface(g_malEngineObjectSL, SL_IID_AUDIOIODEVICECAPABILITIES, &deviceCaps); + if (resultSL != SL_RESULT_SUCCESS) { + // The interface may not be supported so just report a default device. + goto return_default_device; + } + + if (deviceType == mal_device_type_playback) { + SLAudioOutputDescriptor desc; + resultSL = (*deviceCaps)->QueryAudioOutputCapabilities(deviceCaps, pDeviceID->opensl, &desc); + if (resultSL != SL_RESULT_SUCCESS) { + return MAL_NO_DEVICE; + } + + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), (const char*)desc.pDeviceName, (size_t)-1); + } else { + SLAudioInputDescriptor desc; + resultSL = (*deviceCaps)->QueryAudioInputCapabilities(deviceCaps, pDeviceID->opensl, &desc); + if (resultSL != SL_RESULT_SUCCESS) { + return MAL_NO_DEVICE; + } + + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), (const char*)desc.deviceName, (size_t)-1); + } +#else + goto return_default_device; +#endif + +return_default_device: + if (pDeviceID != NULL) { + if ((deviceType == mal_device_type_playback && pDeviceID->opensl != SL_DEFAULTDEVICEID_AUDIOOUTPUT) || + (deviceType == mal_device_type_capture && pDeviceID->opensl != SL_DEFAULTDEVICEID_AUDIOINPUT)) { + return MAL_NO_DEVICE; // Don't know the device. + } + } + + // Name / Description + if (deviceType == mal_device_type_playback) { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } + + return MAL_SUCCESS; +} + mal_result mal_context_init__opensl(mal_context* pContext) { mal_assert(pContext != NULL); - (void)pContext; + + // Initialize global data first if applicable. + if (mal_atomic_increment_32(&g_malOpenSLInitCounter) == 1) { + SLresult resultSL = slCreateEngine(&g_malEngineObjectSL, 0, NULL, 0, NULL, NULL); + if (resultSL != SL_RESULT_SUCCESS) { + mal_atomic_decrement_32(&g_malOpenSLInitCounter); + return MAL_NO_BACKEND; + } + + (*g_malEngineObjectSL)->Realize(g_malEngineObjectSL, SL_BOOLEAN_FALSE); + + resultSL = (*g_malEngineObjectSL)->GetInterface(g_malEngineObjectSL, SL_IID_ENGINE, &g_malEngineSL); + if (resultSL != SL_RESULT_SUCCESS) { + (*g_malEngineObjectSL)->Destroy(g_malEngineObjectSL); + mal_atomic_decrement_32(&g_malOpenSLInitCounter); + return MAL_NO_BACKEND; + } + } + + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__opensl; + pContext->onEnumDevices = mal_context_enumerate_devices__opensl; + pContext->onGetDeviceInfo = mal_context_get_device_info__opensl; + return MAL_SUCCESS; } @@ -7233,109 +12143,12 @@ mal_result mal_context_uninit__opensl(mal_context* pContext) { mal_assert(pContext != NULL); mal_assert(pContext->backend == mal_backend_opensl); - - (void)pContext; - return MAL_SUCCESS; -} - -mal_result mal_enumerate_devices__opensl(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) -{ (void)pContext; - mal_uint32 infoSize = *pCount; - *pCount = 0; - - SLObjectItf engineObj; - SLresult resultSL = slCreateEngine(&engineObj, 0, NULL, 0, NULL, NULL); - if (resultSL != SL_RESULT_SUCCESS) { - return MAL_NO_BACKEND; - } - - (*engineObj)->Realize(engineObj, SL_BOOLEAN_FALSE); - - // TODO: Test Me. - // - // This is currently untested, so for now we are just returning default devices. -#if 0 - SLuint32 pDeviceIDs[128]; - SLint32 deviceCount = sizeof(pDeviceIDs) / sizeof(pDeviceIDs[0]); - - SLAudioIODeviceCapabilitiesItf deviceCaps; - resultSL = (*engineObj)->GetInterface(engineObj, SL_IID_AUDIOIODEVICECAPABILITIES, &deviceCaps); - if (resultSL != SL_RESULT_SUCCESS) { - // The interface may not be supported so just report a default device. - (*engineObj)->Destroy(engineObj); - goto return_default_device; - } - - if (type == mal_device_type_playback) { - resultSL = (*deviceCaps)->GetAvailableAudioOutputs(deviceCaps, &deviceCount, pDeviceIDs); - if (resultSL != SL_RESULT_SUCCESS) { - (*engineObj)->Destroy(engineObj); - return MAL_NO_DEVICE; - } - } else { - resultSL = (*deviceCaps)->GetAvailableAudioInputs(deviceCaps, &deviceCount, pDeviceIDs); - if (resultSL != SL_RESULT_SUCCESS) { - (*engineObj)->Destroy(engineObj); - return MAL_NO_DEVICE; - } - } - - for (SLint32 iDevice = 0; iDevice < deviceCount; ++iDevice) { - if (pInfo != NULL) { - if (infoSize > 0) { - mal_zero_object(pInfo); - pInfo->id.opensl = pDeviceIDs[iDevice]; - - mal_bool32 isValidDevice = MAL_TRUE; - if (type == mal_device_type_playback) { - SLAudioOutputDescriptor desc; - resultSL = (*deviceCaps)->QueryAudioOutputCapabilities(deviceCaps, pInfo->id.opensl, &desc); - if (resultSL != SL_RESULT_SUCCESS) { - isValidDevice = MAL_FALSE; - } - - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), (const char*)desc.pDeviceName, (size_t)-1); - } else { - SLAudioInputDescriptor desc; - resultSL = (*deviceCaps)->QueryAudioInputCapabilities(deviceCaps, pInfo->id.opensl, &desc); - if (resultSL != SL_RESULT_SUCCESS) { - isValidDevice = MAL_FALSE; - } - - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), (const char*)desc.deviceName, (size_t)-1); - } - - if (isValidDevice) { - pInfo += 1; - infoSize -= 1; - *pCount += 1; - } - } - } else { - *pCount += 1; - } - } - - (*engineObj)->Destroy(engineObj); - return MAL_SUCCESS; -#else - (*engineObj)->Destroy(engineObj); - goto return_default_device; -#endif - -return_default_device: - *pCount = 1; - if (pInfo != NULL) { - if (infoSize > 0) { - if (type == mal_device_type_playback) { - pInfo->id.opensl = SL_DEFAULTDEVICEID_AUDIOOUTPUT; - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "Default Playback Device", (size_t)-1); - } else { - pInfo->id.opensl = SL_DEFAULTDEVICEID_AUDIOINPUT; - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "Default Capture Device", (size_t)-1); - } + // Uninit global data. + if (g_malOpenSLInitCounter > 0) { + if (mal_atomic_decrement_32(&g_malOpenSLInitCounter) == 0) { + (*g_malEngineObjectSL)->Destroy(g_malEngineObjectSL); } } @@ -7343,25 +12156,9 @@ return_default_device: } -// OpenSL|ES has one-per-application objects :( -static SLObjectItf g_malEngineObjectSL = NULL; -static SLEngineItf g_malEngineSL = NULL; -static mal_uint32 g_malOpenSLInitCounter = 0; - -#define MAL_OPENSL_OBJ(p) (*((SLObjectItf)(p))) -#define MAL_OPENSL_OUTPUTMIX(p) (*((SLOutputMixItf)(p))) -#define MAL_OPENSL_PLAY(p) (*((SLPlayItf)(p))) -#define MAL_OPENSL_RECORD(p) (*((SLRecordItf)(p))) - #ifdef MAL_ANDROID -#define MAL_OPENSL_BUFFERQUEUE(p) (*((SLAndroidSimpleBufferQueueItf)(p))) -#else -#define MAL_OPENSL_BUFFERQUEUE(p) (*((SLBufferQueueItf)(p))) -#endif - -#ifdef MAL_ANDROID -//static void mal_buffer_queue_callback__opensl_android(SLAndroidSimpleBufferQueueItf pBufferQueue, SLuint32 eventFlags, const void* pBuffer, SLuint32 bufferSize, SLuint32 dataUsed, void* pContext) -static void mal_buffer_queue_callback__opensl_android(SLAndroidSimpleBufferQueueItf pBufferQueue, void* pUserData) +//void mal_buffer_queue_callback__opensl_android(SLAndroidSimpleBufferQueueItf pBufferQueue, SLuint32 eventFlags, const void* pBuffer, SLuint32 bufferSize, SLuint32 dataUsed, void* pContext) +void mal_buffer_queue_callback__opensl_android(SLAndroidSimpleBufferQueueItf pBufferQueue, void* pUserData) { (void)pBufferQueue; @@ -7382,7 +12179,7 @@ static void mal_buffer_queue_callback__opensl_android(SLAndroidSimpleBufferQueue return; } - size_t periodSizeInBytes = pDevice->opensl.periodSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat); + size_t periodSizeInBytes = pDevice->opensl.periodSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat); mal_uint8* pBuffer = pDevice->opensl.pBuffer + (pDevice->opensl.currentBufferIndex * periodSizeInBytes); if (pDevice->type == mal_device_type_playback) { @@ -7409,7 +12206,7 @@ static void mal_buffer_queue_callback__opensl_android(SLAndroidSimpleBufferQueue } #endif -static void mal_device_uninit__opensl(mal_device* pDevice) +void mal_device_uninit__opensl(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -7422,17 +12219,9 @@ static void mal_device_uninit__opensl(mal_device* pDevice) } mal_free(pDevice->opensl.pBuffer); - - - // Uninit global data. - if (g_malOpenSLInitCounter > 0) { - if (mal_atomic_decrement_32(&g_malOpenSLInitCounter) == 0) { - (*g_malEngineObjectSL)->Destroy(g_malEngineObjectSL); - } - } } -static mal_result mal_device_init__opensl(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +mal_result mal_device_init__opensl(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) { (void)pContext; @@ -7448,25 +12237,6 @@ static mal_result mal_device_init__opensl(mal_context* pContext, mal_device_type pDevice->internalFormat = mal_format_s32; } - // Initialize global data first if applicable. - if (mal_atomic_increment_32(&g_malOpenSLInitCounter) == 1) { - SLresult resultSL = slCreateEngine(&g_malEngineObjectSL, 0, NULL, 0, NULL, NULL); - if (resultSL != SL_RESULT_SUCCESS) { - mal_atomic_decrement_32(&g_malOpenSLInitCounter); - return mal_post_error(pDevice, "[OpenSL] slCreateEngine() failed.", MAL_NO_BACKEND); - } - - (*g_malEngineObjectSL)->Realize(g_malEngineObjectSL, SL_BOOLEAN_FALSE); - - resultSL = (*g_malEngineObjectSL)->GetInterface(g_malEngineObjectSL, SL_IID_ENGINE, &g_malEngineSL); - if (resultSL != SL_RESULT_SUCCESS) { - (*g_malEngineObjectSL)->Destroy(g_malEngineObjectSL); - mal_atomic_decrement_32(&g_malOpenSLInitCounter); - return mal_post_error(pDevice, "[OpenSL] Failed to retrieve SL_IID_ENGINE interface.", MAL_NO_BACKEND); - } - } - - // Now we can start initializing the device properly. mal_assert(pDevice != NULL); mal_zero_object(&pDevice->opensl); @@ -7498,7 +12268,7 @@ static mal_result mal_device_init__opensl(mal_context* pContext, mal_device_type pFormat->numChannels = pDevice->channels; pFormat->samplesPerSec = mal_round_to_standard_sample_rate__opensl(pDevice->sampleRate * 1000); // In millihertz. - pFormat->bitsPerSample = mal_get_sample_size_in_bytes(pDevice->format)*8; + pFormat->bitsPerSample = mal_get_bytes_per_sample(pDevice->format)*8; pFormat->containerSize = pFormat->bitsPerSample; // Always tightly packed for now. pFormat->channelMask = mal_channel_map_to_channel_mask__opensl(pConfig->channelMap, pFormat->numChannels); pFormat->endianness = SL_BYTEORDER_LITTLEENDIAN; @@ -7506,7 +12276,7 @@ static mal_result mal_device_init__opensl(mal_context* pContext, mal_device_type // Android has a few restrictions on the format as documented here: https://developer.android.com/ndk/guides/audio/opensl-for-android.html // - Only mono and stereo is supported. // - Only u8 and s16 formats are supported. - // - Limited to a sample rate of 48000. + // - Maximum sample rate of 48000. #ifdef MAL_ANDROID if (pFormat->numChannels > 2) { pFormat->numChannels = 2; @@ -7698,7 +12468,7 @@ static mal_result mal_device_init__opensl(mal_context* pContext, mal_device_type mal_channel_mask_to_channel_map__opensl(pFormat->channelMask, pDevice->internalChannels, pDevice->internalChannelMap); - size_t bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat); + size_t bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat); pDevice->opensl.pBuffer = (mal_uint8*)mal_malloc(bufferSizeInBytes); if (pDevice->opensl.pBuffer == NULL) { mal_device_uninit__opensl(pDevice); @@ -7710,7 +12480,7 @@ static mal_result mal_device_init__opensl(mal_context* pContext, mal_device_type return MAL_SUCCESS; } -static mal_result mal_device__start_backend__opensl(mal_device* pDevice) +mal_result mal_device__start_backend__opensl(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -7723,7 +12493,7 @@ static mal_result mal_device__start_backend__opensl(mal_device* pDevice) // We need to enqueue a buffer for each period. mal_device__read_frames_from_client(pDevice, pDevice->bufferSizeInFrames, pDevice->opensl.pBuffer); - size_t periodSizeInBytes = pDevice->opensl.periodSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat); + size_t periodSizeInBytes = pDevice->opensl.periodSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat); for (mal_uint32 iPeriod = 0; iPeriod < pDevice->periods; ++iPeriod) { resultSL = MAL_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueue)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueue, pDevice->opensl.pBuffer + (periodSizeInBytes * iPeriod), periodSizeInBytes); if (resultSL != SL_RESULT_SUCCESS) { @@ -7737,7 +12507,7 @@ static mal_result mal_device__start_backend__opensl(mal_device* pDevice) return mal_post_error(pDevice, "[OpenSL] Failed to start internal capture device.", MAL_FAILED_TO_START_BACKEND_DEVICE); } - size_t periodSizeInBytes = pDevice->opensl.periodSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat); + size_t periodSizeInBytes = pDevice->opensl.periodSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat); for (mal_uint32 iPeriod = 0; iPeriod < pDevice->periods; ++iPeriod) { resultSL = MAL_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueue)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueue, pDevice->opensl.pBuffer + (periodSizeInBytes * iPeriod), periodSizeInBytes); if (resultSL != SL_RESULT_SUCCESS) { @@ -7750,7 +12520,7 @@ static mal_result mal_device__start_backend__opensl(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__stop_backend__opensl(mal_device* pDevice) +mal_result mal_device__stop_backend__opensl(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -7943,35 +12713,192 @@ typedef void (MAL_AL_APIENTRY * MAL_LPALGETBUFFERI) (mal_ typedef void (MAL_AL_APIENTRY * MAL_LPALGETBUFFER3I) (mal_ALuint buffer, mal_ALenum param, mal_ALint *value1, mal_ALint *value2, mal_ALint *value3); typedef void (MAL_AL_APIENTRY * MAL_LPALGETBUFFERIV) (mal_ALuint buffer, mal_ALenum param, mal_ALint *values); +mal_bool32 mal_context_is_device_id_equal__openal(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) +{ + mal_assert(pContext != NULL); + mal_assert(pID0 != NULL); + mal_assert(pID1 != NULL); + (void)pContext; + + return mal_strcmp(pID0->openal, pID1->openal) == 0; +} + +mal_result mal_context_enumerate_devices__openal(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +{ + mal_assert(pContext != NULL); + mal_assert(callback != NULL); + + if (pContext->openal.isEnumerationSupported) { + mal_bool32 isTerminated = MAL_FALSE; + + // Playback + if (!isTerminated) { + const mal_ALCchar* pPlaybackDeviceNames = ((MAL_LPALCGETSTRING)pContext->openal.alcGetString)(NULL, MAL_ALC_DEVICE_SPECIFIER); + if (pPlaybackDeviceNames == NULL) { + return MAL_NO_DEVICE; + } + + // Each device is stored in pDeviceNames, separated by a null-terminator. The string itself is double-null-terminated. + const mal_ALCchar* pNextPlaybackDeviceName = pPlaybackDeviceNames; + while (pNextPlaybackDeviceName[0] != '\0') { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.id.openal, sizeof(deviceInfo.id.openal), (const char*)pNextPlaybackDeviceName, (size_t)-1); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), (const char*)pNextPlaybackDeviceName, (size_t)-1); + + mal_bool32 cbResult = callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); + if (cbResult == MAL_FALSE) { + isTerminated = MAL_TRUE; + break; + } + + // Move to the next device name. + while (*pNextPlaybackDeviceName != '\0') { + pNextPlaybackDeviceName += 1; + } + + // Skip past the null terminator. + pNextPlaybackDeviceName += 1; + }; + } + + // Capture + if (!isTerminated) { + const mal_ALCchar* pCaptureDeviceNames = ((MAL_LPALCGETSTRING)pContext->openal.alcGetString)(NULL, MAL_ALC_CAPTURE_DEVICE_SPECIFIER); + if (pCaptureDeviceNames == NULL) { + return MAL_NO_DEVICE; + } + + const mal_ALCchar* pNextCaptureDeviceName = pCaptureDeviceNames; + while (pNextCaptureDeviceName[0] != '\0') { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.id.openal, sizeof(deviceInfo.id.openal), (const char*)pNextCaptureDeviceName, (size_t)-1); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), (const char*)pNextCaptureDeviceName, (size_t)-1); + + mal_bool32 cbResult = callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); + if (cbResult == MAL_FALSE) { + isTerminated = MAL_TRUE; + break; + } + + // Move to the next device name. + while (*pNextCaptureDeviceName != '\0') { + pNextCaptureDeviceName += 1; + } + + // Skip past the null terminator. + pNextCaptureDeviceName += 1; + }; + } + } else { + // Enumeration is not supported. Use default devices. + mal_bool32 cbResult = MAL_TRUE; + + // Playback. + if (cbResult) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); + } + + // Capture. + if (cbResult) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); + } + } + + return MAL_SUCCESS; +} + + +typedef struct +{ + mal_device_type deviceType; + const mal_device_id* pDeviceID; + mal_share_mode shareMode; + mal_device_info* pDeviceInfo; + mal_bool32 foundDevice; +} mal_context_get_device_info_enum_callback_data__openal; + +mal_bool32 mal_context_get_device_info_enum_callback__openal(mal_context* pContext, mal_device_type deviceType, const mal_device_info* pDeviceInfo, void* pUserData) +{ + mal_context_get_device_info_enum_callback_data__openal* pData = (mal_context_get_device_info_enum_callback_data__openal*)pUserData; + mal_assert(pData != NULL); + + if (pData->deviceType == deviceType && mal_context_is_device_id_equal__openal(pContext, pData->pDeviceID, &pDeviceInfo->id)) { + mal_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pDeviceInfo->name, (size_t)-1); + pData->foundDevice = MAL_TRUE; + } + + // Keep enumerating until we have found the device. + return !pData->foundDevice; +} + +mal_result mal_context_get_device_info__openal(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +{ + mal_assert(pContext != NULL); + (void)shareMode; + + // Name / Description + if (pDeviceID == NULL) { + if (deviceType == mal_device_type_playback) { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } + + return MAL_SUCCESS; + } else { + mal_context_get_device_info_enum_callback_data__openal data; + data.deviceType = deviceType; + data.pDeviceID = pDeviceID; + data.shareMode = shareMode; + data.pDeviceInfo = pDeviceInfo; + data.foundDevice = MAL_FALSE; + mal_result result = mal_context_enumerate_devices__openal(pContext, mal_context_get_device_info_enum_callback__openal, &data); + if (result != MAL_SUCCESS) { + return result; + } + + if (data.foundDevice) { + return MAL_SUCCESS; + } else { + return MAL_NO_DEVICE; + } + } +} + mal_result mal_context_init__openal(mal_context* pContext) { mal_assert(pContext != NULL); #ifndef MAL_NO_RUNTIME_LINKING - const char* libName = NULL; -#ifdef MAL_WIN32 - libName = "OpenAL32.dll"; + const char* libNames[] = { +#if defined(MAL_WIN32) + "OpenAL32.dll", + "soft_oal.dll" #endif #if defined(MAL_UNIX) && !defined(MAL_APPLE) - libName = "libopenal.so"; + "libopenal.so", + "libopenal.so.1" #endif -#ifdef MAL_APPLE - libName = "OpenAL.framework/OpenAL"; +#if defined(MAL_APPLE) + "OpenAL.framework/OpenAL" #endif - if (libName == NULL) { - return MAL_NO_BACKEND; // Don't know what the library name is called. + }; + + for (size_t i = 0; i < mal_countof(libNames); ++i) { + pContext->openal.hOpenAL = mal_dlopen(libNames[i]); + if (pContext->openal.hOpenAL != NULL) { + break; + } } - - pContext->openal.hOpenAL = mal_dlopen(libName); - -#ifdef MAL_WIN32 - // Special case for Win32 - try "soft_oal.dll" for OpenAL-Soft drop-ins. - if (pContext->openal.hOpenAL == NULL) { - pContext->openal.hOpenAL = mal_dlopen("soft_oal.dll"); - } -#endif - if (pContext->openal.hOpenAL == NULL) { return MAL_FAILED_TO_INIT_BACKEND; } @@ -8137,8 +13064,12 @@ mal_result mal_context_init__openal(mal_context* pContext) // We depend on the ALC_ENUMERATION_EXT extension for enumeration. If this is not supported we fall back to default devices. pContext->openal.isEnumerationSupported = ((MAL_LPALCISEXTENSIONPRESENT)pContext->openal.alcIsExtensionPresent)(NULL, "ALC_ENUMERATION_EXT"); - pContext->openal.isFloat32Supported = ((MAL_LPALISEXTENSIONPRESENT)pContext->openal.alIsExtensionPresent)("AL_EXT_float32"); - pContext->openal.isMCFormatsSupported = ((MAL_LPALISEXTENSIONPRESENT)pContext->openal.alIsExtensionPresent)("AL_EXT_MCFORMATS"); + pContext->openal.isFloat32Supported = ((MAL_LPALISEXTENSIONPRESENT)pContext->openal.alIsExtensionPresent)("AL_EXT_float32"); + pContext->openal.isMCFormatsSupported = ((MAL_LPALISEXTENSIONPRESENT)pContext->openal.alIsExtensionPresent)("AL_EXT_MCFORMATS"); + + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__openal; + pContext->onEnumDevices = mal_context_enumerate_devices__openal; + pContext->onGetDeviceInfo = mal_context_get_device_info__openal; return MAL_SUCCESS; } @@ -8155,68 +13086,22 @@ mal_result mal_context_uninit__openal(mal_context* pContext) return MAL_SUCCESS; } -mal_result mal_enumerate_devices__openal(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) -{ - mal_uint32 infoSize = *pCount; - *pCount = 0; - - if (pContext->openal.isEnumerationSupported) { - const mal_ALCchar* pDeviceNames = ((MAL_LPALCGETSTRING)pContext->openal.alcGetString)(NULL, (type == mal_device_type_playback) ? MAL_ALC_DEVICE_SPECIFIER : MAL_ALC_CAPTURE_DEVICE_SPECIFIER); - if (pDeviceNames == NULL) { - return MAL_NO_DEVICE; - } - - // Each device is stored in pDeviceNames, separated by a null-terminator. The string itself is double-null-terminated. - const mal_ALCchar* pNextDeviceName = pDeviceNames; - while (pNextDeviceName[0] != '\0') { - if (pInfo != NULL) { - if (infoSize > 0) { - mal_strncpy_s(pInfo->id.openal, sizeof(pInfo->id.openal), (const char*)pNextDeviceName, (size_t)-1); - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), (const char*)pNextDeviceName, (size_t)-1); - - pInfo += 1; - infoSize -= 1; - *pCount += 1; - } - } else { - *pCount += 1; - } - - // Move to the next device name. - while (*pNextDeviceName != '\0') { - pNextDeviceName += 1; - } - - // Skip past the null terminator. - pNextDeviceName += 1; - }; - } else { - // Enumeration is not supported. Use default devices. - if (pInfo != NULL) { - if (infoSize > 0) { - if (type == mal_device_type_playback) { - pInfo->id.sdl = 0; - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "Default Playback Device", (size_t)-1); - } else { - pInfo->id.sdl = 0; - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "Default Capture Device", (size_t)-1); - } - - pInfo += 1; - *pCount += 1; - } - } else { - *pCount += 1; - } - } - - return MAL_SUCCESS; -} - void mal_device_uninit__openal(mal_device* pDevice) { mal_assert(pDevice != NULL); + + // Delete buffers and source first. + ((MAL_LPALCMAKECONTEXTCURRENT)pDevice->pContext->openal.alcMakeContextCurrent)((mal_ALCcontext*)pDevice->openal.pContextALC); + if (pDevice->openal.sourceAL != 0) { + ((MAL_LPALDELETESOURCES)pDevice->pContext->openal.alDeleteSources)(1, (const mal_ALuint*)&pDevice->openal.sourceAL); + } + if (pDevice->periods > 0 && pDevice->openal.buffersAL[0] != 0) { + ((MAL_LPALDELETEBUFFERS)pDevice->pContext->openal.alDeleteBuffers)(pDevice->periods, (const mal_ALuint*)pDevice->openal.buffersAL); + } + + + // Now that resources have been deleted we can destroy the OpenAL context and close the device. ((MAL_LPALCMAKECONTEXTCURRENT)pDevice->pContext->openal.alcMakeContextCurrent)(NULL); ((MAL_LPALCDESTROYCONTEXT)pDevice->pContext->openal.alcDestroyContext)((mal_ALCcontext*)pDevice->openal.pContextALC); @@ -8331,7 +13216,6 @@ mal_result mal_device_init__openal(mal_context* pContext, mal_device_type type, pDevice->internalChannels = channelsAL; pDevice->internalSampleRate = frequencyAL; - // The internal format is a little bit straight with OpenAL. switch (formatAL) { case MAL_AL_FORMAT_MONO8: @@ -8454,7 +13338,7 @@ mal_result mal_device_init__openal(mal_context* pContext, mal_device_type type, pDevice->openal.pContextALC = pContextALC; pDevice->openal.formatAL = formatAL; pDevice->openal.subBufferSizeInFrames = pDevice->bufferSizeInFrames / pDevice->periods; - pDevice->openal.pIntermediaryBuffer = (mal_uint8*)mal_malloc(pDevice->openal.subBufferSizeInFrames * channelsAL * mal_get_sample_size_in_bytes(pDevice->internalFormat)); + pDevice->openal.pIntermediaryBuffer = (mal_uint8*)mal_malloc(pDevice->openal.subBufferSizeInFrames * channelsAL * mal_get_bytes_per_sample(pDevice->internalFormat)); if (pDevice->openal.pIntermediaryBuffer == NULL) { mal_device_uninit__openal(pDevice); return mal_context_post_error(pContext, NULL, "[OpenAL] Failed to allocate memory for intermediary buffer.", MAL_OUT_OF_MEMORY); @@ -8463,7 +13347,7 @@ mal_result mal_device_init__openal(mal_context* pContext, mal_device_type type, return MAL_SUCCESS; } -static mal_result mal_device__start_backend__openal(mal_device* pDevice) +mal_result mal_device__start_backend__openal(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -8479,7 +13363,7 @@ static mal_result mal_device__start_backend__openal(mal_device* pDevice) mal_device__read_frames_from_client(pDevice, pDevice->openal.subBufferSizeInFrames, pDevice->openal.pIntermediaryBuffer); mal_ALuint bufferAL = pDevice->openal.buffersAL[i]; - ((MAL_LPALBUFFERDATA)pDevice->pContext->openal.alBufferData)(bufferAL, pDevice->openal.formatAL, pDevice->openal.pIntermediaryBuffer, pDevice->openal.subBufferSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat), pDevice->internalSampleRate); + ((MAL_LPALBUFFERDATA)pDevice->pContext->openal.alBufferData)(bufferAL, pDevice->openal.formatAL, pDevice->openal.pIntermediaryBuffer, pDevice->openal.subBufferSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat), pDevice->internalSampleRate); ((MAL_LPALSOURCEQUEUEBUFFERS)pDevice->pContext->openal.alSourceQueueBuffers)(pDevice->openal.sourceAL, 1, &bufferAL); } @@ -8493,7 +13377,7 @@ static mal_result mal_device__start_backend__openal(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__stop_backend__openal(mal_device* pDevice) +mal_result mal_device__stop_backend__openal(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -8507,7 +13391,7 @@ static mal_result mal_device__stop_backend__openal(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__break_main_loop__openal(mal_device* pDevice) +mal_result mal_device__break_main_loop__openal(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -8515,7 +13399,7 @@ static mal_result mal_device__break_main_loop__openal(mal_device* pDevice) return MAL_SUCCESS; } -static mal_uint32 mal_device__get_available_frames__openal(mal_device* pDevice) +mal_uint32 mal_device__get_available_frames__openal(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -8534,7 +13418,7 @@ static mal_uint32 mal_device__get_available_frames__openal(mal_device* pDevice) } } -static mal_uint32 mal_device__wait_for_frames__openal(mal_device* pDevice) +mal_uint32 mal_device__wait_for_frames__openal(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -8555,7 +13439,7 @@ static mal_uint32 mal_device__wait_for_frames__openal(mal_device* pDevice) } } -static mal_result mal_device__main_loop__openal(mal_device* pDevice) +mal_result mal_device__main_loop__openal(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -8582,7 +13466,7 @@ static mal_result mal_device__main_loop__openal(mal_device* pDevice) ((MAL_LPALCMAKECONTEXTCURRENT)pDevice->pContext->openal.alcMakeContextCurrent)((mal_ALCcontext*)pDevice->openal.pContextALC); ((MAL_LPALSOURCEUNQUEUEBUFFERS)pDevice->pContext->openal.alSourceUnqueueBuffers)(pDevice->openal.sourceAL, 1, &bufferAL); - ((MAL_LPALBUFFERDATA)pDevice->pContext->openal.alBufferData)(bufferAL, pDevice->openal.formatAL, pDevice->openal.pIntermediaryBuffer, pDevice->openal.subBufferSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat), pDevice->internalSampleRate); + ((MAL_LPALBUFFERDATA)pDevice->pContext->openal.alBufferData)(bufferAL, pDevice->openal.formatAL, pDevice->openal.pIntermediaryBuffer, pDevice->openal.subBufferSizeInFrames * pDevice->internalChannels * mal_get_bytes_per_sample(pDevice->internalFormat), pDevice->internalSampleRate); ((MAL_LPALSOURCEQUEUEBUFFERS)pDevice->pContext->openal.alSourceQueueBuffers)(pDevice->openal.sourceAL, 1, &bufferAL); framesAvailable -= framesToRead; @@ -8706,6 +13590,120 @@ mal_format mal_format_from_sdl(MAL_SDL_AudioFormat format) } } +mal_bool32 mal_context_is_device_id_equal__sdl(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) +{ + mal_assert(pContext != NULL); + mal_assert(pID0 != NULL); + mal_assert(pID1 != NULL); + (void)pContext; + + return pID0->sdl == pID1->sdl; +} + +mal_result mal_context_enumerate_devices__sdl(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) +{ + mal_assert(pContext != NULL); + mal_assert(callback != NULL); + +#ifndef MAL_USE_SDL_1 + if (!pContext->sdl.usingSDL1) { + mal_bool32 isTerminated = MAL_FALSE; + + // Playback + if (!isTerminated) { + int deviceCount = ((MAL_PFN_SDL_GetNumAudioDevices)pContext->sdl.SDL_GetNumAudioDevices)(0); + for (int i = 0; i < deviceCount; ++i) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + + deviceInfo.id.sdl = i; + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ((MAL_PFN_SDL_GetAudioDeviceName)pContext->sdl.SDL_GetAudioDeviceName)(i, 0), (size_t)-1); + + mal_bool32 cbResult = callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); + if (cbResult == MAL_FALSE) { + isTerminated = MAL_TRUE; + break; + } + } + } + + // Capture + if (!isTerminated) { + int deviceCount = ((MAL_PFN_SDL_GetNumAudioDevices)pContext->sdl.SDL_GetNumAudioDevices)(1); + for (int i = 0; i < deviceCount; ++i) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + + deviceInfo.id.sdl = i; + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ((MAL_PFN_SDL_GetAudioDeviceName)pContext->sdl.SDL_GetAudioDeviceName)(i, 1), (size_t)-1); + + mal_bool32 cbResult = callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); + if (cbResult == MAL_FALSE) { + isTerminated = MAL_TRUE; + break; + } + } + } + } else +#endif + { + // SDL1 only uses default devices. + mal_bool32 cbResult = MAL_TRUE; + + // Playback. + if (cbResult) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, mal_device_type_playback, &deviceInfo, pUserData); + } + + // Capture. + if (cbResult) { + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + mal_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + cbResult = callback(pContext, mal_device_type_capture, &deviceInfo, pUserData); + } + } + + return MAL_SUCCESS; +} + +mal_result mal_context_get_device_info__sdl(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +{ + mal_assert(pContext != NULL); + (void)shareMode; + +#ifndef MAL_USE_SDL_1 + if (!pContext->sdl.usingSDL1) { + if (pDeviceID == NULL) { + if (deviceType == mal_device_type_playback) { + pDeviceInfo->id.sdl = 0; + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + pDeviceInfo->id.sdl = 0; + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } + } else { + pDeviceInfo->id.sdl = pDeviceID->sdl; + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), ((MAL_PFN_SDL_GetAudioDeviceName)pContext->sdl.SDL_GetAudioDeviceName)(pDeviceID->sdl, (deviceType == mal_device_type_playback) ? 0 : 1), (size_t)-1); + } + } else +#endif + { + // SDL1 uses default devices. + if (deviceType == mal_device_type_playback) { + pDeviceInfo->id.sdl = 0; + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + pDeviceInfo->id.sdl = 0; + mal_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } + } + + return MAL_SUCCESS; +} mal_result mal_context_init__sdl(mal_context* pContext) { @@ -8780,6 +13778,10 @@ mal_result mal_context_init__sdl(mal_context* pContext) return MAL_ERROR; } + pContext->onDeviceIDEqual = mal_context_is_device_id_equal__sdl; + pContext->onEnumDevices = mal_context_enumerate_devices__sdl; + pContext->onGetDeviceInfo = mal_context_get_device_info__sdl; + return MAL_SUCCESS; } @@ -8792,54 +13794,6 @@ mal_result mal_context_uninit__sdl(mal_context* pContext) return MAL_SUCCESS; } -mal_result mal_enumerate_devices__sdl(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) -{ - (void)pContext; - - mal_uint32 infoSize = *pCount; - *pCount = 0; - -#ifndef MAL_USE_SDL_1 - if (!pContext->sdl.usingSDL1) { - int deviceCount = ((MAL_PFN_SDL_GetNumAudioDevices)pContext->sdl.SDL_GetNumAudioDevices)((type == mal_device_type_playback) ? 0 : 1); - for (int i = 0; i < deviceCount; ++i) { - if (pInfo != NULL) { - if (infoSize > 0) { - pInfo->id.sdl = i; - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), ((MAL_PFN_SDL_GetAudioDeviceName)pContext->sdl.SDL_GetAudioDeviceName)(i, (type == mal_device_type_playback) ? 0 : 1), (size_t)-1); - - pInfo += 1; - *pCount += 1; - } - } else { - *pCount += 1; - } - } - } else -#endif - { - if (pInfo != NULL) { - if (infoSize > 0) { - // SDL1 uses default devices. - if (type == mal_device_type_playback) { - pInfo->id.sdl = 0; - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "Default Playback Device", (size_t)-1); - } else { - pInfo->id.sdl = 0; - mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "Default Capture Device", (size_t)-1); - } - - pInfo += 1; - *pCount += 1; - } - } else { - *pCount += 1; - } - } - - return MAL_SUCCESS; -} - void mal_device_uninit__sdl(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -8855,12 +13809,12 @@ void mal_device_uninit__sdl(mal_device* pDevice) } -static void mal_audio_callback__sdl(void* pUserData, mal_uint8* pBuffer, int bufferSizeInBytes) +void mal_audio_callback__sdl(void* pUserData, mal_uint8* pBuffer, int bufferSizeInBytes) { mal_device* pDevice = (mal_device*)pUserData; mal_assert(pDevice != NULL); - mal_uint32 bufferSizeInFrames = (mal_uint32)bufferSizeInBytes / mal_get_sample_size_in_bytes(pDevice->internalFormat) / pDevice->internalChannels; + mal_uint32 bufferSizeInFrames = (mal_uint32)bufferSizeInBytes / mal_get_bytes_per_sample(pDevice->internalFormat) / pDevice->internalChannels; if (pDevice->type == mal_device_type_playback) { mal_device__read_frames_from_client(pDevice, bufferSizeInFrames, pBuffer); @@ -8956,7 +13910,7 @@ mal_result mal_device_init__sdl(mal_context* pContext, mal_device_type type, mal return MAL_SUCCESS; } -static mal_result mal_device__start_backend__sdl(mal_device* pDevice) +mal_result mal_device__start_backend__sdl(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -8972,7 +13926,7 @@ static mal_result mal_device__start_backend__sdl(mal_device* pDevice) return MAL_SUCCESS; } -static mal_result mal_device__stop_backend__sdl(mal_device* pDevice) +mal_result mal_device__stop_backend__sdl(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -8994,13 +13948,18 @@ static mal_result mal_device__stop_backend__sdl(mal_device* pDevice) mal_bool32 mal__is_channel_map_valid(const mal_channel* channelMap, mal_uint32 channels) { - mal_assert(channels > 0); + // A blank channel map should be allowed, in which case it should use an appropriate default which will depend on context. + if (channelMap[0] != MAL_CHANNEL_NONE) { + if (channels == 0) { + return MAL_FALSE; // No channels. + } - // A channel cannot be present in the channel map more than once. - for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { - for (mal_uint32 jChannel = iChannel + 1; jChannel < channels; ++jChannel) { - if (channelMap[iChannel] == channelMap[jChannel]) { - return MAL_FALSE; + // A channel cannot be present in the channel map more than once. + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + for (mal_uint32 jChannel = iChannel + 1; jChannel < channels; ++jChannel) { + if (channelMap[iChannel] == channelMap[jChannel]) { + return MAL_FALSE; + } } } } @@ -9009,7 +13968,7 @@ mal_bool32 mal__is_channel_map_valid(const mal_channel* channelMap, mal_uint32 c } -static mal_result mal_device__start_backend(mal_device* pDevice) +mal_result mal_device__start_backend(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -9034,6 +13993,11 @@ static mal_result mal_device__start_backend(mal_device* pDevice) result = mal_device__start_backend__alsa(pDevice); } #endif +#ifdef MAL_HAS_PULSEAUDIO + if (pDevice->pContext->backend == mal_backend_pulseaudio) { + result = mal_device__start_backend__pulse(pDevice); + } +#endif #ifdef MAL_HAS_OSS if (pDevice->pContext->backend == mal_backend_oss) { result = mal_device__start_backend__oss(pDevice); @@ -9053,7 +14017,7 @@ static mal_result mal_device__start_backend(mal_device* pDevice) return result; } -static mal_result mal_device__stop_backend(mal_device* pDevice) +mal_result mal_device__stop_backend(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -9078,6 +14042,11 @@ static mal_result mal_device__stop_backend(mal_device* pDevice) result = mal_device__stop_backend__alsa(pDevice); } #endif +#ifdef MAL_HAS_PULSEAUDIO + if (pDevice->pContext->backend == mal_backend_pulseaudio) { + result = mal_device__stop_backend__pulse(pDevice); + } +#endif #ifdef MAL_HAS_OSS if (pDevice->pContext->backend == mal_backend_oss) { result = mal_device__stop_backend__oss(pDevice); @@ -9097,7 +14066,7 @@ static mal_result mal_device__stop_backend(mal_device* pDevice) return result; } -static mal_result mal_device__break_main_loop(mal_device* pDevice) +mal_result mal_device__break_main_loop(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -9122,6 +14091,11 @@ static mal_result mal_device__break_main_loop(mal_device* pDevice) result = mal_device__break_main_loop__alsa(pDevice); } #endif +#ifdef MAL_HAS_PULSEAUDIO + if (pDevice->pContext->backend == mal_backend_pulseaudio) { + result = mal_device__break_main_loop__pulse(pDevice); + } +#endif #ifdef MAL_HAS_OSS if (pDevice->pContext->backend == mal_backend_oss) { result = mal_device__break_main_loop__oss(pDevice); @@ -9141,7 +14115,7 @@ static mal_result mal_device__break_main_loop(mal_device* pDevice) return result; } -static mal_result mal_device__main_loop(mal_device* pDevice) +mal_result mal_device__main_loop(mal_device* pDevice) { mal_assert(pDevice != NULL); @@ -9166,6 +14140,11 @@ static mal_result mal_device__main_loop(mal_device* pDevice) result = mal_device__main_loop__alsa(pDevice); } #endif +#ifdef MAL_HAS_PULSEAUDIO + if (pDevice->pContext->backend == mal_backend_pulseaudio) { + result = mal_device__main_loop__pulse(pDevice); + } +#endif #ifdef MAL_HAS_OSS if (pDevice->pContext->backend == mal_backend_oss) { result = mal_device__main_loop__oss(pDevice); @@ -9289,6 +14268,7 @@ mal_result mal_context_init_backend_apis__win32(mal_context* pContext) pContext->win32.CoCreateInstance = (mal_proc)mal_dlsym(pContext->win32.hOle32DLL, "CoCreateInstance"); pContext->win32.CoTaskMemFree = (mal_proc)mal_dlsym(pContext->win32.hOle32DLL, "CoTaskMemFree"); pContext->win32.PropVariantClear = (mal_proc)mal_dlsym(pContext->win32.hOle32DLL, "PropVariantClear"); + pContext->win32.StringFromGUID2 = (mal_proc)mal_dlsym(pContext->win32.hOle32DLL, "StringFromGUID2"); // User32.dll @@ -9307,7 +14287,11 @@ mal_result mal_context_init_backend_apis__win32(mal_context* pContext) #else mal_result mal_context_uninit_backend_apis__nix(mal_context* pContext) { +#if defined(MAL_USE_RUNTIME_LINKING_FOR_PTHREAD) && !defined(MAL_NO_RUNTIME_LINKING) mal_dlclose(pContext->posix.pthreadSO); +#else + (void)pContext; +#endif return MAL_SUCCESS; } @@ -9315,7 +14299,7 @@ mal_result mal_context_uninit_backend_apis__nix(mal_context* pContext) mal_result mal_context_init_backend_apis__nix(mal_context* pContext) { // pthread -#if !defined(MAL_NO_RUNTIME_LINKING) +#if defined(MAL_USE_RUNTIME_LINKING_FOR_PTHREAD) && !defined(MAL_NO_RUNTIME_LINKING) const char* libpthreadFileNames[] = { "libpthread.so", "libpthread.so.0", @@ -9333,27 +14317,37 @@ mal_result mal_context_init_backend_apis__nix(mal_context* pContext) return MAL_FAILED_TO_INIT_BACKEND; } - pContext->posix.pthread_create = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_create"); - pContext->posix.pthread_join = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_join"); - pContext->posix.pthread_mutex_init = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_mutex_init"); - pContext->posix.pthread_mutex_destroy = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_mutex_destroy"); - pContext->posix.pthread_mutex_lock = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_mutex_lock"); - pContext->posix.pthread_mutex_unlock = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_mutex_unlock"); - pContext->posix.pthread_cond_init = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_cond_init"); - pContext->posix.pthread_cond_destroy = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_cond_destroy"); - pContext->posix.pthread_cond_wait = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_cond_wait"); - pContext->posix.pthread_cond_signal = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_cond_signal"); + pContext->posix.pthread_create = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_create"); + pContext->posix.pthread_join = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_join"); + pContext->posix.pthread_mutex_init = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_mutex_init"); + pContext->posix.pthread_mutex_destroy = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_mutex_destroy"); + pContext->posix.pthread_mutex_lock = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_mutex_lock"); + pContext->posix.pthread_mutex_unlock = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_mutex_unlock"); + pContext->posix.pthread_cond_init = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_cond_init"); + pContext->posix.pthread_cond_destroy = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_cond_destroy"); + pContext->posix.pthread_cond_wait = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_cond_wait"); + pContext->posix.pthread_cond_signal = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_cond_signal"); + pContext->posix.pthread_attr_init = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_attr_init"); + pContext->posix.pthread_attr_destroy = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_attr_destroy"); + pContext->posix.pthread_attr_setschedpolicy = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_attr_setschedpolicy"); + pContext->posix.pthread_attr_getschedparam = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_attr_getschedparam"); + pContext->posix.pthread_attr_setschedparam = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_attr_setschedparam"); #else - pContext->posix.pthread_create = (mal_proc)pthread_create; - pContext->posix.pthread_join = (mal_proc)pthread_join; - pContext->posix.pthread_mutex_init = (mal_proc)pthread_mutex_init; - pContext->posix.pthread_mutex_destroy = (mal_proc)pthread_mutex_destroy; - pContext->posix.pthread_mutex_lock = (mal_proc)pthread_mutex_lock; - pContext->posix.pthread_mutex_unlock = (mal_proc)pthread_mutex_unlock; - pContext->posix.pthread_cond_init = (mal_proc)pthread_cond_init; - pContext->posix.pthread_cond_destroy = (mal_proc)pthread_cond_destroy; - pContext->posix.pthread_cond_wait = (mal_proc)pthread_cond_wait; - pContext->posix.pthread_cond_signal = (mal_proc)pthread_cond_signal; + pContext->posix.pthread_create = (mal_proc)pthread_create; + pContext->posix.pthread_join = (mal_proc)pthread_join; + pContext->posix.pthread_mutex_init = (mal_proc)pthread_mutex_init; + pContext->posix.pthread_mutex_destroy = (mal_proc)pthread_mutex_destroy; + pContext->posix.pthread_mutex_lock = (mal_proc)pthread_mutex_lock; + pContext->posix.pthread_mutex_unlock = (mal_proc)pthread_mutex_unlock; + pContext->posix.pthread_cond_init = (mal_proc)pthread_cond_init; + pContext->posix.pthread_cond_destroy = (mal_proc)pthread_cond_destroy; + pContext->posix.pthread_cond_wait = (mal_proc)pthread_cond_wait; + pContext->posix.pthread_cond_signal = (mal_proc)pthread_cond_signal; + pContext->posix.pthread_attr_init = (mal_proc)pthread_attr_init; + pContext->posix.pthread_attr_destroy = (mal_proc)pthread_attr_destroy; + pContext->posix.pthread_attr_setschedpolicy = (mal_proc)pthread_attr_setschedpolicy; + pContext->posix.pthread_attr_getschedparam = (mal_proc)pthread_attr_getschedparam; + pContext->posix.pthread_attr_setschedparam = (mal_proc)pthread_attr_setschedparam; #endif return MAL_SUCCESS; @@ -9362,7 +14356,7 @@ mal_result mal_context_init_backend_apis__nix(mal_context* pContext) mal_result mal_context_init_backend_apis(mal_context* pContext) { - mal_result result = MAL_NO_BACKEND; + mal_result result; #ifdef MAL_WIN32 result = mal_context_init_backend_apis__win32(pContext); #else @@ -9374,7 +14368,7 @@ mal_result mal_context_init_backend_apis(mal_context* pContext) mal_result mal_context_uninit_backend_apis(mal_context* pContext) { - mal_result result = MAL_NO_BACKEND; + mal_result result; #ifdef MAL_WIN32 result = mal_context_uninit_backend_apis__win32(pContext); #else @@ -9384,9 +14378,34 @@ mal_result mal_context_uninit_backend_apis(mal_context* pContext) return result; } -mal_result mal_context_init(mal_backend backends[], mal_uint32 backendCount, const mal_context_config* pConfig, mal_context* pContext) +const mal_backend g_malDefaultBackends[] = { + mal_backend_wasapi, + mal_backend_dsound, + mal_backend_winmm, + mal_backend_oss, + mal_backend_pulseaudio, + mal_backend_alsa, + mal_backend_jack, + mal_backend_opensl, + mal_backend_openal, + mal_backend_sdl, + mal_backend_null +}; + +mal_bool32 mal_is_backend_asynchronous(mal_backend backend) { - if (pContext == NULL) return MAL_INVALID_ARGS; + return + backend == mal_backend_jack || + backend == mal_backend_opensl || + backend == mal_backend_sdl; +} + +mal_result mal_context_init(const mal_backend backends[], mal_uint32 backendCount, const mal_context_config* pConfig, mal_context* pContext) +{ + if (pContext == NULL) { + return MAL_INVALID_ARGS; + } + mal_zero_object(pContext); // Always make sure the config is set first to ensure properties are available as soon as possible. @@ -9402,21 +14421,9 @@ mal_result mal_context_init(mal_backend backends[], mal_uint32 backendCount, con return result; } - static mal_backend defaultBackends[] = { - mal_backend_wasapi, - mal_backend_dsound, - mal_backend_winmm, - mal_backend_alsa, - mal_backend_oss, - mal_backend_opensl, - mal_backend_openal, - mal_backend_sdl, - mal_backend_null - }; - if (backends == NULL) { - backends = defaultBackends; - backendCount = sizeof(defaultBackends) / sizeof(defaultBackends[0]); + backends = g_malDefaultBackends; + backendCount = mal_countof(g_malDefaultBackends); } mal_assert(backends != NULL); @@ -9450,6 +14457,18 @@ mal_result mal_context_init(mal_backend backends[], mal_uint32 backendCount, con result = mal_context_init__alsa(pContext); } break; #endif + #ifdef MAL_HAS_PULSEAUDIO + case mal_backend_pulseaudio: + { + result = mal_context_init__pulse(pContext); + } break; + #endif + #ifdef MAL_HAS_JACK + case mal_backend_jack: + { + result = mal_context_init__jack(pContext); + } break; + #endif #ifdef MAL_HAS_OSS case mal_backend_oss: { @@ -9486,72 +14505,96 @@ mal_result mal_context_init(mal_backend backends[], mal_uint32 backendCount, con // If this iteration was successful, return. if (result == MAL_SUCCESS) { + result = mal_mutex_init(pContext, &pContext->deviceEnumLock); + if (result != MAL_SUCCESS) { + mal_context_post_error(pContext, NULL, "WARNING: Failed to initialize mutex for device enumeration. mal_context_get_devices() is not thread safe.", MAL_FAILED_TO_CREATE_MUTEX); + } + result = mal_mutex_init(pContext, &pContext->deviceInfoLock); + if (result != MAL_SUCCESS) { + mal_context_post_error(pContext, NULL, "WARNING: Failed to initialize mutex for device info retrieval. mal_context_get_device_info() is not thread safe.", MAL_FAILED_TO_CREATE_MUTEX); + } + pContext->backend = backend; return result; } } + // If we get here it means an error occurred. mal_zero_object(pContext); // Safety. return MAL_NO_BACKEND; } mal_result mal_context_uninit(mal_context* pContext) { - if (pContext == NULL) return MAL_INVALID_ARGS; + if (pContext == NULL) { + return MAL_INVALID_ARGS; + } switch (pContext->backend) { #ifdef MAL_HAS_WASAPI case mal_backend_wasapi: { - return mal_context_uninit__wasapi(pContext); + mal_context_uninit__wasapi(pContext); } break; #endif #ifdef MAL_HAS_DSOUND case mal_backend_dsound: { - return mal_context_uninit__dsound(pContext); + mal_context_uninit__dsound(pContext); } break; #endif #ifdef MAL_HAS_WINMM case mal_backend_winmm: { - return mal_context_uninit__winmm(pContext); + mal_context_uninit__winmm(pContext); } break; #endif #ifdef MAL_HAS_ALSA case mal_backend_alsa: { - return mal_context_uninit__alsa(pContext); + mal_context_uninit__alsa(pContext); + } break; + #endif + #ifdef MAL_HAS_PULSEAUDIO + case mal_backend_pulseaudio: + { + mal_context_uninit__pulse(pContext); + } break; + #endif + #ifdef MAL_HAS_JACK + case mal_backend_jack: + { + mal_context_uninit__jack(pContext); } break; #endif #ifdef MAL_HAS_OSS case mal_backend_oss: { - return mal_context_uninit__oss(pContext); + mal_context_uninit__oss(pContext); } break; #endif #ifdef MAL_HAS_OPENSL case mal_backend_opensl: { - return mal_context_uninit__opensl(pContext); + mal_context_uninit__opensl(pContext); } break; #endif #ifdef MAL_HAS_OPENAL case mal_backend_openal: { - return mal_context_uninit__openal(pContext); + mal_context_uninit__openal(pContext); } break; #endif #ifdef MAL_HAS_SDL case mal_backend_sdl: { - return mal_context_uninit__sdl(pContext); + mal_context_uninit__sdl(pContext); } break; #endif #ifdef MAL_HAS_NULL case mal_backend_null: { - return mal_context_uninit__null(pContext); + mal_context_uninit__null(pContext); } break; #endif @@ -9559,96 +14602,180 @@ mal_result mal_context_uninit(mal_context* pContext) } mal_context_uninit_backend_apis(pContext); + mal_mutex_uninit(&pContext->deviceEnumLock); + mal_mutex_uninit(&pContext->deviceInfoLock); + mal_free(pContext->pDeviceInfos); - mal_assert(MAL_FALSE); - return MAL_NO_BACKEND; + return MAL_SUCCESS; } -mal_result mal_enumerate_devices(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) +mal_result mal_context_enumerate_devices(mal_context* pContext, mal_enum_devices_callback_proc callback, void* pUserData) { - if (pCount == NULL) return mal_post_error(NULL, "mal_enumerate_devices() called with invalid arguments (pCount == 0).", MAL_INVALID_ARGS); - - // The output buffer needs to be initialized to zero. - if (pInfo != NULL) { - mal_zero_memory(pInfo, (*pCount) * sizeof(*pInfo)); + if (pContext == NULL || pContext->onEnumDevices == NULL || callback == NULL) { + return MAL_INVALID_ARGS; } - switch (pContext->backend) + mal_result result; + mal_mutex_lock(&pContext->deviceEnumLock); { - #ifdef MAL_HAS_WASAPI - case mal_backend_wasapi: - { - return mal_enumerate_devices__wasapi(pContext, type, pCount, pInfo); - } break; - #endif - #ifdef MAL_HAS_DSOUND - case mal_backend_dsound: - { - return mal_enumerate_devices__dsound(pContext, type, pCount, pInfo); - } break; - #endif - #ifdef MAL_HAS_WINMM - case mal_backend_winmm: - { - return mal_enumerate_devices__winmm(pContext, type, pCount, pInfo); - } break; - #endif - #ifdef MAL_HAS_ALSA - case mal_backend_alsa: - { - return mal_enumerate_devices__alsa(pContext, type, pCount, pInfo); - } break; - #endif - #ifdef MAL_HAS_OSS - case mal_backend_oss: - { - return mal_enumerate_devices__oss(pContext, type, pCount, pInfo); - } break; - #endif - #ifdef MAL_HAS_OPENSL - case mal_backend_opensl: - { - return mal_enumerate_devices__opensl(pContext, type, pCount, pInfo); - } break; - #endif - #ifdef MAL_HAS_OPENAL - case mal_backend_openal: - { - return mal_enumerate_devices__openal(pContext, type, pCount, pInfo); - } break; - #endif - #ifdef MAL_HAS_SDL - case mal_backend_sdl: - { - return mal_enumerate_devices__sdl(pContext, type, pCount, pInfo); - } break; - #endif - #ifdef MAL_HAS_NULL - case mal_backend_null: - { - return mal_enumerate_devices__null(pContext, type, pCount, pInfo); - } break; - #endif + result = pContext->onEnumDevices(pContext, callback, pUserData); + } + mal_mutex_unlock(&pContext->deviceEnumLock); - default: break; + return result; +} + + +mal_bool32 mal_context_get_devices__enum_callback(mal_context* pContext, mal_device_type type, const mal_device_info* pInfo, void* pUserData) +{ + (void)pUserData; + + // We need to insert the device info into our main internal buffer. Where it goes depends on the device type. If it's a capture device + // it's just appended to the end. If it's a playback device it's inserted just before the first capture device. + + // First make sure we have room. Since the number of devices we add to the list is usually relatively small I've decided to use a + // simple fixed size increment for buffer expansion. + const mal_uint32 bufferExpansionCount = 2; + const mal_uint32 totalDeviceInfoCount = pContext->playbackDeviceInfoCount + pContext->captureDeviceInfoCount; + + if (pContext->deviceInfoCapacity >= totalDeviceInfoCount) { + mal_uint32 newCapacity = totalDeviceInfoCount + bufferExpansionCount; + mal_device_info* pNewInfos = (mal_device_info*)mal_realloc(pContext->pDeviceInfos, sizeof(*pContext->pDeviceInfos)*newCapacity); + if (pNewInfos == NULL) { + return MAL_FALSE; // Out of memory. + } + + pContext->pDeviceInfos = pNewInfos; + pContext->deviceInfoCapacity = newCapacity; } - mal_assert(MAL_FALSE); - return MAL_NO_BACKEND; + if (type == mal_device_type_playback) { + // Playback. Insert just before the first capture device. + + // The first thing to do is move all of the capture devices down a slot. + mal_uint32 iFirstCaptureDevice = pContext->playbackDeviceInfoCount; + for (size_t iCaptureDevice = totalDeviceInfoCount; iCaptureDevice > iFirstCaptureDevice; --iCaptureDevice) { + pContext->pDeviceInfos[iCaptureDevice] = pContext->pDeviceInfos[iCaptureDevice-1]; + } + + // Now just insert where the first capture device was before moving it down a slot. + pContext->pDeviceInfos[iFirstCaptureDevice] = *pInfo; + pContext->playbackDeviceInfoCount += 1; + } else { + // Capture. Insert at the end. + pContext->pDeviceInfos[totalDeviceInfoCount] = *pInfo; + pContext->captureDeviceInfoCount += 1; + } + + return MAL_TRUE; } +mal_result mal_context_get_devices(mal_context* pContext, mal_device_info** ppPlaybackDeviceInfos, mal_uint32* pPlaybackDeviceCount, mal_device_info** ppCaptureDeviceInfos, mal_uint32* pCaptureDeviceCount) +{ + // Safety. + if (ppPlaybackDeviceInfos != NULL) *ppPlaybackDeviceInfos = NULL; + if (pPlaybackDeviceCount != NULL) *pPlaybackDeviceCount = 0; + if (ppCaptureDeviceInfos != NULL) *ppCaptureDeviceInfos = NULL; + if (pCaptureDeviceCount != NULL) *pCaptureDeviceCount = 0; + + if (pContext == NULL || pContext->onEnumDevices == NULL) { + return MAL_INVALID_ARGS; + } + + // Note that we don't use mal_context_enumerate_devices() here because we want to do locking at a higher level. + mal_result result; + mal_mutex_lock(&pContext->deviceEnumLock); + { + // Reset everything first. + pContext->playbackDeviceInfoCount = 0; + pContext->captureDeviceInfoCount = 0; + + // Now enumerate over available devices. + result = pContext->onEnumDevices(pContext, mal_context_get_devices__enum_callback, NULL); + if (result == MAL_SUCCESS) { + // Playback devices. + if (ppPlaybackDeviceInfos != NULL) { + *ppPlaybackDeviceInfos = pContext->pDeviceInfos; + } + if (pPlaybackDeviceCount != NULL) { + *pPlaybackDeviceCount = pContext->playbackDeviceInfoCount; + } + + // Capture devices. + if (ppCaptureDeviceInfos != NULL) { + *ppCaptureDeviceInfos = pContext->pDeviceInfos + pContext->playbackDeviceInfoCount; // Capture devices come after playback devices. + } + if (pCaptureDeviceCount != NULL) { + *pCaptureDeviceCount = pContext->captureDeviceInfoCount; + } + } + } + mal_mutex_unlock(&pContext->deviceEnumLock); + + return result; +} + +mal_result mal_context_get_device_info(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, mal_share_mode shareMode, mal_device_info* pDeviceInfo) +{ + // NOTE: Do not clear pDeviceInfo on entry. The reason is the pDeviceID may actually point to pDeviceInfo->id which will break things. + if (pContext == NULL || pDeviceInfo == NULL) { + return MAL_INVALID_ARGS; + } + + mal_device_info deviceInfo; + mal_zero_object(&deviceInfo); + + // Help the backend out by copying over the device ID if we have one. + if (pDeviceID != NULL) { + mal_copy_memory(&deviceInfo.id, pDeviceID, sizeof(*pDeviceID)); + } + + // The backend may have an optimized device info retrieval function. If so, try that first. + if (pContext->onGetDeviceInfo != NULL) { + mal_result result; + mal_mutex_lock(&pContext->deviceInfoLock); + { + result = pContext->onGetDeviceInfo(pContext, type, pDeviceID, shareMode, &deviceInfo); + } + mal_mutex_unlock(&pContext->deviceInfoLock); + + *pDeviceInfo = deviceInfo; + return result; + } + + // Getting here means onGetDeviceInfo has not been set. + return MAL_ERROR; +} + + mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, void* pUserData, mal_device* pDevice) { + if (pContext == NULL) { + return mal_device_init_ex(NULL, 0, NULL, type, pDeviceID, pConfig, pUserData, pDevice); + } + + if (pDevice == NULL) { return mal_post_error(pDevice, "mal_device_init() called with invalid arguments (pDevice == NULL).", MAL_INVALID_ARGS); } + + // The config is allowed to be NULL, in which case we default to mal_device_config_init_default(). + mal_device_config config; if (pConfig == NULL) { - return mal_post_error(pDevice, "mal_device_init() called with invalid arguments (pConfig == NULL).", MAL_INVALID_ARGS); + config = mal_device_config_init_default(); + } else { + config = *pConfig; + } + + // Basic config validation. + if (config.channels > MAL_MAX_CHANNELS) { + return mal_post_error(pDevice, "mal_device_init() called with an invalid config. Channel count cannot exceed 32.", MAL_INVALID_DEVICE_CONFIG); + } + if (!mal__is_channel_map_valid(config.channelMap, config.channels)) { + return mal_post_error(pDevice, "mal_device_init() called with invalid config. Channel map is invalid.", MAL_INVALID_DEVICE_CONFIG); } - // Make a copy of the config to ensure we don't override the caller's object. - mal_device_config config = *pConfig; mal_zero_object(pDevice); pDevice->pContext = pContext; @@ -9666,25 +14793,22 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi } - if (pContext == NULL) { - return mal_post_error(pDevice, "mal_device_init() called with invalid arguments (pContext == NULL).", MAL_INVALID_ARGS); + // When passing in 0 for the format/channels/rate/chmap it means the device will be using whatever is chosen by the backend. If everything is set + // to defaults it means the format conversion pipeline will run on a fast path where data transfer is just passed straight through to the backend. + if (config.format == mal_format_unknown) { + config.format = MAL_DEFAULT_FORMAT; + pDevice->usingDefaultFormat = MAL_TRUE; } - - - // Basic config validation. if (config.channels == 0) { - return mal_post_error(pDevice, "mal_device_init() called with an invalid config. Channel count must be greater than 0.", MAL_INVALID_DEVICE_CONFIG); + config.channels = MAL_DEFAULT_CHANNELS; + pDevice->usingDefaultChannels = MAL_TRUE; } - if (config.channels > MAL_MAX_CHANNELS) { - return mal_post_error(pDevice, "mal_device_init() called with an invalid config. Channel count cannot exceed 18.", MAL_INVALID_DEVICE_CONFIG); - } - if (config.sampleRate == 0) { - return mal_post_error(pDevice, "mal_device_init() called with an invalid config. Sample rate must be greater than 0.", MAL_INVALID_DEVICE_CONFIG); + config.sampleRate = MAL_DEFAULT_SAMPLE_RATE; + pDevice->usingDefaultSampleRate = MAL_TRUE; } - - if (!mal__is_channel_map_valid(pConfig->channelMap, pConfig->channels)) { - return mal_post_error(pDevice, "mal_device_init() called with invalid arguments. Channel map is invalid.", MAL_INVALID_DEVICE_CONFIG); + if (config.channelMap[0] == MAL_CHANNEL_NONE) { + pDevice->usingDefaultChannelMap = MAL_TRUE; } @@ -9701,8 +14825,8 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi pDevice->type = type; pDevice->format = config.format; pDevice->channels = config.channels; - mal_copy_memory(config.channelMap, config.channelMap, sizeof(config.channelMap[0]) * config.channels); pDevice->sampleRate = config.sampleRate; + mal_copy_memory(pDevice->channelMap, config.channelMap, sizeof(config.channelMap[0]) * config.channels); pDevice->bufferSizeInFrames = config.bufferSizeInFrames; pDevice->periods = config.periods; @@ -9765,6 +14889,18 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi result = mal_device_init__alsa(pContext, type, pDeviceID, &config, pDevice); } break; #endif + #ifdef MAL_HAS_PULSEAUDIO + case mal_backend_pulseaudio: + { + result = mal_device_init__pulse(pContext, type, pDeviceID, &config, pDevice); + } break; + #endif + #ifdef MAL_HAS_JACK + case mal_backend_jack: + { + result = mal_device_init__jack(pContext, type, pDeviceID, &config, pDevice); + } break; + #endif #ifdef MAL_HAS_OSS case mal_backend_oss: { @@ -9810,9 +14946,9 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi // We failed to get the device name, so fall back to some generic names. if (pDeviceID == NULL) { if (type == mal_device_type_playback) { - mal_strncpy_s(pDevice->name, sizeof(pDevice->name), "Default Playback Device", (size_t)-1); + mal_strncpy_s(pDevice->name, sizeof(pDevice->name), MAL_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); } else { - mal_strncpy_s(pDevice->name, sizeof(pDevice->name), "Default Capture Device", (size_t)-1); + mal_strncpy_s(pDevice->name, sizeof(pDevice->name), MAL_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); } } else { if (type == mal_device_type_playback) { @@ -9824,11 +14960,25 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi } } + // If the format/channels/rate is using defaults we need to set these to be the same as the internal config. + if (pDevice->usingDefaultFormat) { + pDevice->format = pDevice->internalFormat; + } + if (pDevice->usingDefaultChannels) { + pDevice->channels = pDevice->internalChannels; + } + if (pDevice->usingDefaultSampleRate) { + pDevice->sampleRate = pDevice->internalSampleRate; + } + if (pDevice->usingDefaultChannelMap) { + mal_copy_memory(pDevice->channelMap, pDevice->internalChannelMap, sizeof(pDevice->channelMap)); + } + // We need a DSP object which is where samples are moved through in order to convert them to the // format required by the backend. - mal_dsp_config dspConfig; - dspConfig.cacheSizeInFrames = pDevice->bufferSizeInFrames; + mal_dsp_config dspConfig = mal_dsp_config_init_new(); + dspConfig.pUserData = pDevice; if (type == mal_device_type_playback) { dspConfig.formatIn = pDevice->format; dspConfig.channelsIn = pDevice->channels; @@ -9838,7 +14988,8 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi dspConfig.channelsOut = pDevice->internalChannels; dspConfig.sampleRateOut = pDevice->internalSampleRate; mal_copy_memory(dspConfig.channelMapOut, pDevice->internalChannelMap, sizeof(dspConfig.channelMapOut)); - mal_dsp_init(&dspConfig, mal_device__on_read_from_client, pDevice, &pDevice->dsp); + dspConfig.onRead = mal_device__on_read_from_client; + mal_dsp_init(&dspConfig, &pDevice->dsp); } else { dspConfig.formatIn = pDevice->internalFormat; dspConfig.channelsIn = pDevice->internalChannels; @@ -9848,14 +14999,14 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi dspConfig.channelsOut = pDevice->channels; dspConfig.sampleRateOut = pDevice->sampleRate; mal_copy_memory(dspConfig.channelMapOut, pDevice->channelMap, sizeof(dspConfig.channelMapOut)); - mal_dsp_init(&dspConfig, mal_device__on_read_from_device, pDevice, &pDevice->dsp); + dspConfig.onRead = mal_device__on_read_from_device; + mal_dsp_init(&dspConfig, &pDevice->dsp); } - // Some backends don't require the worker thread. - if (pContext->backend != mal_backend_opensl && pContext->backend != mal_backend_sdl) { + if (!mal_is_backend_asynchronous(pContext->backend)) { // The worker thread. if (mal_thread_create(pContext, &pDevice->thread, mal_worker_thread, pDevice) != MAL_SUCCESS) { mal_device_uninit(pDevice); @@ -9872,6 +15023,41 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi return MAL_SUCCESS; } +mal_result mal_device_init_ex(const mal_backend backends[], mal_uint32 backendCount, const mal_context_config* pContextConfig, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, void* pUserData, mal_device* pDevice) +{ + mal_context* pContext = (mal_context*)mal_malloc(sizeof(*pContext)); + if (pContext == NULL) { + return MAL_OUT_OF_MEMORY; + } + + if (backends == NULL) { + backends = g_malDefaultBackends; + backendCount = mal_countof(g_malDefaultBackends); + } + + mal_result result = MAL_NO_BACKEND; + + for (mal_uint32 iBackend = 0; iBackend < backendCount; ++iBackend) { + result = mal_context_init(&backends[iBackend], 1, pContextConfig, pContext); + if (result == MAL_SUCCESS) { + result = mal_device_init(pContext, type, pDeviceID, pConfig, pUserData, pDevice); + if (result == MAL_SUCCESS) { + break; // Success. + } else { + mal_context_uninit(pContext); // Failure. + } + } + } + + if (result != MAL_SUCCESS) { + mal_free(pContext); + return result; + } + + pDevice->isOwnerOfContext = MAL_TRUE; + return result; +} + void mal_device_uninit(mal_device* pDevice) { if (!mal_device__is_initialized(pDevice)) return; @@ -9888,7 +15074,7 @@ void mal_device_uninit(mal_device* pDevice) mal_device__set_state(pDevice, MAL_STATE_UNINITIALIZED); // Wake up the worker thread and wait for it to properly terminate. - if (pDevice->pContext->backend != mal_backend_opensl && pDevice->pContext->backend != mal_backend_sdl) { + if (!mal_is_backend_asynchronous(pDevice->pContext->backend)) { mal_event_signal(&pDevice->wakeupEvent); mal_thread_wait(&pDevice->thread); } @@ -9918,6 +15104,16 @@ void mal_device_uninit(mal_device* pDevice) mal_device_uninit__alsa(pDevice); } #endif +#ifdef MAL_HAS_PULSEAUDIO + if (pDevice->pContext->backend == mal_backend_pulseaudio) { + mal_device_uninit__pulse(pDevice); + } +#endif +#ifdef MAL_HAS_JACK + if (pDevice->pContext->backend == mal_backend_jack) { + mal_device_uninit__jack(pDevice); + } +#endif #ifdef MAL_HAS_OSS if (pDevice->pContext->backend == mal_backend_oss) { mal_device_uninit__oss(pDevice); @@ -9944,6 +15140,12 @@ void mal_device_uninit(mal_device* pDevice) } #endif + + if (pDevice->isOwnerOfContext) { + mal_context_uninit(pDevice->pContext); + mal_free(pDevice->pContext); + } + mal_zero_object(pDevice); } @@ -9993,6 +15195,14 @@ mal_result mal_device_start(mal_device* pDevice) mal_device__set_state(pDevice, MAL_STATE_STARTING); // Asynchronous backends need to be handled differently. +#ifdef MAL_HAS_JACK + if (pDevice->pContext->backend == mal_backend_jack) { + result = mal_device__start_backend__jack(pDevice); + if (result == MAL_SUCCESS) { + mal_device__set_state(pDevice, MAL_STATE_STARTED); + } + } else +#endif #ifdef MAL_HAS_OPENSL if (pDevice->pContext->backend == mal_backend_opensl) { result = mal_device__start_backend__opensl(pDevice); @@ -10054,6 +15264,11 @@ mal_result mal_device_stop(mal_device* pDevice) // There's no need to wake up the thread like we do when starting. // Asynchronous backends need to be handled differently. +#ifdef MAL_HAS_JACK + if (pDevice->pContext->backend == mal_backend_jack) { + mal_device__stop_backend__jack(pDevice); + } else +#endif #ifdef MAL_HAS_OPENSL if (pDevice->pContext->backend == mal_backend_opensl) { mal_device__stop_backend__opensl(pDevice); @@ -10090,10 +15305,10 @@ mal_bool32 mal_device_is_started(mal_device* pDevice) mal_uint32 mal_device_get_buffer_size_in_bytes(mal_device* pDevice) { if (pDevice == NULL) return 0; - return pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format); + return pDevice->bufferSizeInFrames * pDevice->channels * mal_get_bytes_per_sample(pDevice->format); } -mal_uint32 mal_get_sample_size_in_bytes(mal_format format) +mal_uint32 mal_get_bytes_per_sample(mal_format format) { mal_uint32 sizes[] = { 0, // unknown @@ -10116,341 +15331,3149 @@ mal_context_config mal_context_config_init(mal_log_proc onLog) return config; } -mal_device_config mal_device_config_init(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_recv_proc onRecvCallback, mal_send_proc onSendCallback) + +mal_device_config mal_device_config_init_default() { mal_device_config config; mal_zero_object(&config); + return config; +} + +mal_device_config mal_device_config_init_default_capture(mal_recv_proc onRecvCallback) +{ + mal_device_config config = mal_device_config_init_default(); + config.onRecvCallback = onRecvCallback; + + return config; +} + +mal_device_config mal_device_config_init_default_playback(mal_send_proc onSendCallback) +{ + mal_device_config config = mal_device_config_init_default(); + config.onSendCallback = onSendCallback; + + return config; +} + + +mal_device_config mal_device_config_init_ex(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_channel channelMap[MAL_MAX_CHANNELS], mal_recv_proc onRecvCallback, mal_send_proc onSendCallback) +{ + mal_device_config config = mal_device_config_init_default(); + config.format = format; config.channels = channels; config.sampleRate = sampleRate; config.onRecvCallback = onRecvCallback; config.onSendCallback = onSendCallback; - switch (channels) - { - case 1: - { - config.channelMap[0] = MAL_CHANNEL_FRONT_CENTER; - } break; - - case 2: - { - config.channelMap[0] = MAL_CHANNEL_FRONT_LEFT; - config.channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - config.channelMap[0] = MAL_CHANNEL_FRONT_LEFT; - config.channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; - config.channelMap[2] = MAL_CHANNEL_LFE; - } break; - - case 4: - { - config.channelMap[0] = MAL_CHANNEL_FRONT_LEFT; - config.channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; - config.channelMap[2] = MAL_CHANNEL_BACK_LEFT; - config.channelMap[3] = MAL_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - config.channelMap[0] = MAL_CHANNEL_FRONT_LEFT; - config.channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; - config.channelMap[2] = MAL_CHANNEL_BACK_LEFT; - config.channelMap[3] = MAL_CHANNEL_BACK_RIGHT; - config.channelMap[4] = MAL_CHANNEL_LFE; - } break; - - case 6: - { - config.channelMap[0] = MAL_CHANNEL_FRONT_LEFT; - config.channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; - config.channelMap[2] = MAL_CHANNEL_FRONT_CENTER; - config.channelMap[3] = MAL_CHANNEL_LFE; - config.channelMap[4] = MAL_CHANNEL_BACK_LEFT; - config.channelMap[5] = MAL_CHANNEL_BACK_RIGHT; - } break; - - case 8: - { - config.channelMap[0] = MAL_CHANNEL_FRONT_LEFT; - config.channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; - config.channelMap[2] = MAL_CHANNEL_FRONT_CENTER; - config.channelMap[3] = MAL_CHANNEL_LFE; - config.channelMap[4] = MAL_CHANNEL_BACK_LEFT; - config.channelMap[5] = MAL_CHANNEL_BACK_RIGHT; - config.channelMap[6] = MAL_CHANNEL_SIDE_LEFT; - config.channelMap[7] = MAL_CHANNEL_SIDE_RIGHT; - } break; - - default: - { - // Just leave it all blank in this case. This will use the same mapping as the device's native mapping. - } break; + if (channelMap == NULL) { + if (channels > 8) { + mal_zero_memory(channelMap, sizeof(mal_channel)*MAL_MAX_CHANNELS); + } else { + mal_get_standard_channel_map(mal_standard_channel_map_default, channels, config.channelMap); + } + } else { + mal_copy_memory(config.channelMap, channelMap, sizeof(config.channelMap)); } return config; } +void mal_get_standard_channel_map_microsoft(mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]) +{ + // Based off the speaker configurations mentioned here: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ksmedia/ns-ksmedia-ksaudio_channel_config + switch (channels) + { + case 1: + { + channelMap[0] = MAL_CHANNEL_MONO; + } break; + + case 2: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + } break; + + case 3: // Not defined, but best guess. + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + } break; + + case 4: + { +#ifndef MAL_USE_QUAD_MICROSOFT_CHANNEL_MAP + // Surround. Using the Surround profile has the advantage of the 3rd channel (MAL_CHANNEL_FRONT_CENTER) mapping nicely + // with higher channel counts. + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + channelMap[3] = MAL_CHANNEL_BACK_CENTER; +#else + // Quad. + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_BACK_LEFT; + channelMap[3] = MAL_CHANNEL_BACK_RIGHT; +#endif + } break; + + case 5: // Not defined, but best guess. + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + channelMap[3] = MAL_CHANNEL_BACK_LEFT; + channelMap[4] = MAL_CHANNEL_BACK_RIGHT; + } break; + + case 6: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + channelMap[3] = MAL_CHANNEL_LFE; + channelMap[4] = MAL_CHANNEL_SIDE_LEFT; + channelMap[5] = MAL_CHANNEL_SIDE_RIGHT; + } break; + + case 7: // Not defined, but best guess. + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + channelMap[3] = MAL_CHANNEL_LFE; + channelMap[4] = MAL_CHANNEL_BACK_CENTER; + channelMap[5] = MAL_CHANNEL_SIDE_LEFT; + channelMap[6] = MAL_CHANNEL_SIDE_RIGHT; + } break; + + case 8: + default: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + channelMap[3] = MAL_CHANNEL_LFE; + channelMap[4] = MAL_CHANNEL_BACK_LEFT; + channelMap[5] = MAL_CHANNEL_BACK_RIGHT; + channelMap[6] = MAL_CHANNEL_SIDE_LEFT; + channelMap[7] = MAL_CHANNEL_SIDE_RIGHT; + } break; + } + + // Remainder. + if (channels > 8) { + for (mal_uint32 iChannel = 8; iChannel < MAL_MAX_CHANNELS; ++iChannel) { + channelMap[iChannel] = (mal_channel)(MAL_CHANNEL_AUX_0 + (iChannel-8)); + } + } +} + +void mal_get_standard_channel_map_alsa(mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]) +{ + switch (channels) + { + case 1: + { + channelMap[0] = MAL_CHANNEL_MONO; + } break; + + case 2: + { + channelMap[0] = MAL_CHANNEL_LEFT; + channelMap[1] = MAL_CHANNEL_RIGHT; + } break; + + case 3: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + } break; + + case 4: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_BACK_LEFT; + channelMap[3] = MAL_CHANNEL_BACK_RIGHT; + } break; + + case 5: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_BACK_LEFT; + channelMap[3] = MAL_CHANNEL_BACK_RIGHT; + channelMap[4] = MAL_CHANNEL_FRONT_CENTER; + } break; + + case 6: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_BACK_LEFT; + channelMap[3] = MAL_CHANNEL_BACK_RIGHT; + channelMap[4] = MAL_CHANNEL_FRONT_CENTER; + channelMap[5] = MAL_CHANNEL_LFE; + } break; + + case 7: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_BACK_LEFT; + channelMap[3] = MAL_CHANNEL_BACK_RIGHT; + channelMap[4] = MAL_CHANNEL_FRONT_CENTER; + channelMap[5] = MAL_CHANNEL_LFE; + channelMap[6] = MAL_CHANNEL_BACK_CENTER; + } break; + + case 8: + default: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_BACK_LEFT; + channelMap[3] = MAL_CHANNEL_BACK_RIGHT; + channelMap[4] = MAL_CHANNEL_FRONT_CENTER; + channelMap[5] = MAL_CHANNEL_LFE; + channelMap[6] = MAL_CHANNEL_SIDE_LEFT; + channelMap[7] = MAL_CHANNEL_SIDE_RIGHT; + } break; + } + + // Remainder. + if (channels > 8) { + for (mal_uint32 iChannel = 8; iChannel < MAL_MAX_CHANNELS; ++iChannel) { + channelMap[iChannel] = (mal_channel)(MAL_CHANNEL_AUX_0 + (iChannel-8)); + } + } +} + +void mal_get_standard_channel_map_rfc3551(mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]) +{ + switch (channels) + { + case 1: + { + channelMap[0] = MAL_CHANNEL_MONO; + } break; + + case 2: + { + channelMap[0] = MAL_CHANNEL_LEFT; + channelMap[1] = MAL_CHANNEL_RIGHT; + } break; + + case 3: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + } break; + + case 4: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_CENTER; + channelMap[2] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[3] = MAL_CHANNEL_BACK_CENTER; + } break; + + case 5: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + channelMap[3] = MAL_CHANNEL_BACK_LEFT; + channelMap[4] = MAL_CHANNEL_BACK_RIGHT; + } break; + + case 6: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_SIDE_LEFT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + channelMap[3] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[4] = MAL_CHANNEL_SIDE_RIGHT; + channelMap[5] = MAL_CHANNEL_BACK_CENTER; + } break; + } + + // Remainder. + if (channels > 8) { + for (mal_uint32 iChannel = 6; iChannel < MAL_MAX_CHANNELS; ++iChannel) { + channelMap[iChannel] = (mal_channel)(MAL_CHANNEL_AUX_0 + (iChannel-6)); + } + } +} + +void mal_get_standard_channel_map_flac(mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]) +{ + switch (channels) + { + case 1: + { + channelMap[0] = MAL_CHANNEL_MONO; + } break; + + case 2: + { + channelMap[0] = MAL_CHANNEL_LEFT; + channelMap[1] = MAL_CHANNEL_RIGHT; + } break; + + case 3: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + } break; + + case 4: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_BACK_LEFT; + channelMap[3] = MAL_CHANNEL_BACK_RIGHT; + } break; + + case 5: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + channelMap[3] = MAL_CHANNEL_BACK_LEFT; + channelMap[4] = MAL_CHANNEL_BACK_RIGHT; + } break; + + case 6: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + channelMap[3] = MAL_CHANNEL_LFE; + channelMap[4] = MAL_CHANNEL_BACK_LEFT; + channelMap[5] = MAL_CHANNEL_BACK_RIGHT; + } break; + + case 7: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + channelMap[3] = MAL_CHANNEL_LFE; + channelMap[4] = MAL_CHANNEL_BACK_CENTER; + channelMap[5] = MAL_CHANNEL_SIDE_LEFT; + channelMap[6] = MAL_CHANNEL_SIDE_RIGHT; + } break; + + case 8: + default: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + channelMap[3] = MAL_CHANNEL_LFE; + channelMap[4] = MAL_CHANNEL_BACK_LEFT; + channelMap[5] = MAL_CHANNEL_BACK_RIGHT; + channelMap[6] = MAL_CHANNEL_SIDE_LEFT; + channelMap[7] = MAL_CHANNEL_SIDE_RIGHT; + } break; + } + + // Remainder. + if (channels > 8) { + for (mal_uint32 iChannel = 8; iChannel < MAL_MAX_CHANNELS; ++iChannel) { + channelMap[iChannel] = (mal_channel)(MAL_CHANNEL_AUX_0 + (iChannel-8)); + } + } +} + +void mal_get_standard_channel_map_vorbis(mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]) +{ + // In Vorbis' type 0 channel mapping, the first two channels are not always the standard left/right - it + // will have the center speaker where the right usually goes. Why?! + switch (channels) + { + case 1: + { + channelMap[0] = MAL_CHANNEL_MONO; + } break; + + case 2: + { + channelMap[0] = MAL_CHANNEL_LEFT; + channelMap[1] = MAL_CHANNEL_RIGHT; + } break; + + case 3: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_CENTER; + channelMap[2] = MAL_CHANNEL_FRONT_RIGHT; + } break; + + case 4: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_BACK_LEFT; + channelMap[3] = MAL_CHANNEL_BACK_RIGHT; + } break; + + case 5: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_CENTER; + channelMap[2] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[3] = MAL_CHANNEL_BACK_LEFT; + channelMap[4] = MAL_CHANNEL_BACK_RIGHT; + } break; + + case 6: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_CENTER; + channelMap[2] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[3] = MAL_CHANNEL_BACK_LEFT; + channelMap[4] = MAL_CHANNEL_BACK_RIGHT; + channelMap[5] = MAL_CHANNEL_LFE; + } break; + + case 7: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_CENTER; + channelMap[2] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[3] = MAL_CHANNEL_SIDE_LEFT; + channelMap[4] = MAL_CHANNEL_SIDE_RIGHT; + channelMap[5] = MAL_CHANNEL_BACK_CENTER; + channelMap[6] = MAL_CHANNEL_LFE; + } break; + + case 8: + default: + { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_CENTER; + channelMap[2] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[3] = MAL_CHANNEL_SIDE_LEFT; + channelMap[4] = MAL_CHANNEL_SIDE_RIGHT; + channelMap[5] = MAL_CHANNEL_BACK_LEFT; + channelMap[6] = MAL_CHANNEL_BACK_RIGHT; + channelMap[7] = MAL_CHANNEL_LFE; + } break; + } + + // Remainder. + if (channels > 8) { + for (mal_uint32 iChannel = 8; iChannel < MAL_MAX_CHANNELS; ++iChannel) { + channelMap[iChannel] = (mal_channel)(MAL_CHANNEL_AUX_0 + (iChannel-8)); + } + } +} + +void mal_get_standard_channel_map(mal_standard_channel_map standardChannelMap, mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]) +{ + switch (standardChannelMap) + { + case mal_standard_channel_map_alsa: + { + mal_get_standard_channel_map_alsa(channels, channelMap); + } break; + + case mal_standard_channel_map_rfc3551: + { + mal_get_standard_channel_map_rfc3551(channels, channelMap); + } break; + + case mal_standard_channel_map_flac: + { + mal_get_standard_channel_map_flac(channels, channelMap); + } break; + + case mal_standard_channel_map_vorbis: + { + mal_get_standard_channel_map_vorbis(channels, channelMap); + } break; + + case mal_standard_channel_map_microsoft: + default: + { + mal_get_standard_channel_map_microsoft(channels, channelMap); + } break; + } +} + +void mal_channel_map_copy(mal_channel* pOut, mal_channel* pIn, mal_uint32 channels) +{ + if (pOut != NULL && pIn != NULL && channels > 0) { + mal_copy_memory(pOut, pIn, sizeof(*pOut) * channels); + } +} + +mal_bool32 mal_channel_map_valid(mal_uint32 channels, const mal_channel channelMap[MAL_MAX_CHANNELS]) +{ + if (channelMap == NULL) { + return MAL_FALSE; + } + + // A channel count of 0 is invalid. + if (channels == 0) { + return MAL_FALSE; + } + + // It does not make sense to have a mono channel when there is more than 1 channel. + if (channels > 1) { + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + if (channelMap[iChannel] == MAL_CHANNEL_MONO) { + return MAL_FALSE; + } + } + } + + return MAL_TRUE; +} + +mal_bool32 mal_channel_map_equal(mal_uint32 channels, const mal_channel channelMapA[MAL_MAX_CHANNELS], const mal_channel channelMapB[MAL_MAX_CHANNELS]) +{ + if (channelMapA == channelMapB) { + return MAL_FALSE; + } + + if (channels == 0 || channels > MAL_MAX_CHANNELS) { + return MAL_FALSE; + } + + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + if (channelMapA[iChannel] != channelMapB[iChannel]) { + return MAL_FALSE; + } + } + + return MAL_TRUE; +} + +mal_bool32 mal_channel_map_blank(mal_uint32 channels, const mal_channel channelMap[MAL_MAX_CHANNELS]) +{ + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + if (channelMap[iChannel] != MAL_CHANNEL_NONE) { + return MAL_FALSE; + } + } + + return MAL_TRUE; +} + +mal_bool32 mal_channel_map_contains_channel_position(mal_uint32 channels, const mal_channel channelMap[MAL_MAX_CHANNELS], mal_channel channelPosition) +{ + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + if (channelMap[iChannel] == channelPosition) { + return MAL_TRUE; + } + } + + return MAL_FALSE; +} + + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Format Conversion. +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//#define MAL_USE_REFERENCE_CONVERSION_APIS 1 +//#define MAL_USE_SSE + +void mal_copy_memory_64(void* dst, const void* src, mal_uint64 sizeInBytes) +{ +#if 0xFFFFFFFFFFFFFFFF <= SIZE_MAX + mal_copy_memory(dst, src, (size_t)sizeInBytes); +#else + while (sizeInBytes > 0) { + mal_uint64 bytesToCopyNow = sizeInBytes; + if (bytesToCopyNow > SIZE_MAX) { + bytesToCopyNow = SIZE_MAX; + } + + mal_copy_memory(dst, src, (size_t)bytesToCopyNow); // Safe cast to size_t. + + sizeInBytes -= bytesToCopyNow; + dst = ( void*)(( mal_uint8*)dst + bytesToCopyNow); + src = (const void*)((const mal_uint8*)src + bytesToCopyNow); + } +#endif +} + +void mal_zero_memory_64(void* dst, mal_uint64 sizeInBytes) +{ +#if 0xFFFFFFFFFFFFFFFF <= SIZE_MAX + mal_zero_memory(dst, (size_t)sizeInBytes); +#else + while (sizeInBytes > 0) { + mal_uint64 bytesToZeroNow = sizeInBytes; + if (bytesToZeroNow > SIZE_MAX) { + bytesToZeroNow = SIZE_MAX; + } + + mal_zero_memory(dst, (size_t)bytesToZeroNow); // Safe cast to size_t. + + sizeInBytes -= bytesToZeroNow; + dst = (void*)((mal_uint8*)dst + bytesToZeroNow); + } +#endif +} + + +// u8 +void mal_pcm_u8_to_u8(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + mal_copy_memory_64(dst, src, count * sizeof(mal_uint8)); +} + + +void mal_pcm_u8_to_s16__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_int16* dst_s16 = (mal_int16*)dst; + const mal_uint8* src_u8 = (const mal_uint8*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + mal_int16 x = src_u8[i]; + x = x - 128; + x = x << 8; + dst_s16[i] = x; + } +} + +void mal_pcm_u8_to_s16__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_u8_to_s16__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_u8_to_s16__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_u8_to_s16__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_u8_to_s16(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_u8_to_s16__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_u8_to_s16__sse(dst, src, count, ditherMode); +#else + mal_pcm_u8_to_s16__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_u8_to_s24__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_uint8* dst_s24 = (mal_uint8*)dst; + const mal_uint8* src_u8 = (const mal_uint8*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + mal_int16 x = src_u8[i]; + x = x - 128; + + dst_s24[i*3+0] = 0; + dst_s24[i*3+1] = 0; + dst_s24[i*3+2] = (mal_uint8)((mal_int8)x); + } +} + +void mal_pcm_u8_to_s24__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_u8_to_s24__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_u8_to_s24__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_u8_to_s24__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_u8_to_s24(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_u8_to_s24__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_u8_to_s24__sse(dst, src, count, ditherMode); +#else + mal_pcm_u8_to_s24__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_u8_to_s32__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_int32* dst_s32 = (mal_int32*)dst; + const mal_uint8* src_u8 = (const mal_uint8*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + mal_int32 x = src_u8[i]; + x = x - 128; + x = x << 24; + dst_s32[i] = x; + } +} + +void mal_pcm_u8_to_s32__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_u8_to_s32__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_u8_to_s32__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_u8_to_s32__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_u8_to_s32(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_u8_to_s32__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_u8_to_s32__sse(dst, src, count, ditherMode); +#else + mal_pcm_u8_to_s32__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_u8_to_f32__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + float* dst_f32 = (float*)dst; + const mal_uint8* src_u8 = (const mal_uint8*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + float x = (float)src_u8[i]; + x = x * 0.00784313725490196078f; // 0..255 to 0..2 + x = x - 1; // 0..2 to -1..1 + + dst_f32[i] = x; + } +} + +void mal_pcm_u8_to_f32__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_u8_to_f32__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_u8_to_f32__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_u8_to_f32__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_u8_to_f32(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_u8_to_f32__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_u8_to_f32__sse(dst, src, count, ditherMode); +#else + mal_pcm_u8_to_f32__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + + +void mal_pcm_interleave_u8__reference(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_uint8* dst_u8 = (mal_uint8*)dst; + const mal_uint8** src_u8 = (const mal_uint8**)src; + + mal_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + mal_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_u8[iFrame*channels + iChannel] = src_u8[iChannel][iFrame]; + } + } +} + +void mal_pcm_interleave_u8__optimized(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_uint8* dst_u8 = (mal_uint8*)dst; + const mal_uint8** src_u8 = (const mal_uint8**)src; + + if (channels == 1) { + mal_copy_memory_64(dst, src[0], frameCount * sizeof(mal_uint8)); + } else if (channels == 2) { + mal_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + dst_u8[iFrame*2 + 0] = src_u8[0][iFrame]; + dst_u8[iFrame*2 + 1] = src_u8[1][iFrame]; + } + } else { + mal_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + mal_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_u8[iFrame*channels + iChannel] = src_u8[iChannel][iFrame]; + } + } + } +} + +void mal_pcm_interleave_u8(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_interleave_u8__reference(dst, src, frameCount, channels); +#else + mal_pcm_interleave_u8__optimized(dst, src, frameCount, channels); +#endif +} + + +void mal_pcm_deinterleave_u8__reference(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_uint8** dst_u8 = (mal_uint8**)dst; + const mal_uint8* src_u8 = (const mal_uint8*)src; + + mal_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + mal_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_u8[iChannel][iFrame] = src_u8[iFrame*channels + iChannel]; + } + } +} + +void mal_pcm_deinterleave_u8__optimized(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_pcm_deinterleave_u8__reference(dst, src, frameCount, channels); +} + +void mal_pcm_deinterleave_u8(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_deinterleave_u8__reference(dst, src, frameCount, channels); +#else + mal_pcm_deinterleave_u8__optimized(dst, src, frameCount, channels); +#endif +} + + +// s16 +void mal_pcm_s16_to_u8__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_uint8* dst_u8 = (mal_uint8*)dst; + const mal_int16* src_s16 = (const mal_int16*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + mal_int16 x = src_s16[i]; + x = x >> 8; + x = x + 128; + dst_u8[i] = (mal_uint8)x; + } +} + +void mal_pcm_s16_to_u8__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s16_to_u8__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_s16_to_u8__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s16_to_u8__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_s16_to_u8(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_s16_to_u8__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_s16_to_u8__sse(dst, src, count, ditherMode); +#else + mal_pcm_s16_to_u8__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_s16_to_s16(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + mal_copy_memory_64(dst, src, count * sizeof(mal_int16)); +} + + +void mal_pcm_s16_to_s24__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_uint8* dst_s24 = (mal_uint8*)dst; + const mal_int16* src_s16 = (const mal_int16*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + dst_s24[i*3+0] = 0; + dst_s24[i*3+1] = (mal_uint8)(src_s16[i] & 0xFF); + dst_s24[i*3+2] = (mal_uint8)(src_s16[i] >> 8); + } +} + +void mal_pcm_s16_to_s24__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s16_to_s24__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_s16_to_s24__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s16_to_s24__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_s16_to_s24(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_s16_to_s24__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_s16_to_s24__sse(dst, src, count, ditherMode); +#else + mal_pcm_s16_to_s24__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_s16_to_s32__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_int32* dst_s32 = (mal_int32*)dst; + const mal_int16* src_s16 = (const mal_int16*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + dst_s32[i] = src_s16[i] << 16; + } +} + +void mal_pcm_s16_to_s32__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s16_to_s32__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_s16_to_s32__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s16_to_s32__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_s16_to_s32(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_s16_to_s32__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_s16_to_s32__sse(dst, src, count, ditherMode); +#else + mal_pcm_s16_to_s32__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_s16_to_f32__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + float* dst_f32 = (float*)dst; + const mal_int16* src_s16 = (const mal_int16*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + float x = (float)src_s16[i]; + +#if 0 + // The accurate way. + x = x + 32768.0f; // -32768..32767 to 0..65535 + x = x * 0.00003051804379339284f; // 0..65536 to 0..2 + x = x - 1; // 0..2 to -1..1 +#else + // The fast way. + x = x * 0.000030517578125f; // -32768..32767 to -1..0.999969482421875 +#endif + + dst_f32[i] = x; + } +} + +void mal_pcm_s16_to_f32__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s16_to_f32__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_s16_to_f32__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s16_to_f32__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_s16_to_f32(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_s16_to_f32__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_s16_to_f32__sse(dst, src, count, ditherMode); +#else + mal_pcm_s16_to_f32__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_interleave_s16__reference(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_int16* dst_s16 = (mal_int16*)dst; + const mal_int16** src_s16 = (const mal_int16**)src; + + mal_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + mal_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_s16[iFrame*channels + iChannel] = src_s16[iChannel][iFrame]; + } + } +} + +void mal_pcm_interleave_s16__optimized(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_pcm_interleave_s16__reference(dst, src, frameCount, channels); +} + +void mal_pcm_interleave_s16(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_interleave_s16__reference(dst, src, frameCount, channels); +#else + mal_pcm_interleave_s16__optimized(dst, src, frameCount, channels); +#endif +} + + +void mal_pcm_deinterleave_s16__reference(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_int16** dst_s16 = (mal_int16**)dst; + const mal_int16* src_s16 = (const mal_int16*)src; + + mal_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + mal_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_s16[iChannel][iFrame] = src_s16[iFrame*channels + iChannel]; + } + } +} + +void mal_pcm_deinterleave_s16__optimized(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_pcm_deinterleave_s16__reference(dst, src, frameCount, channels); +} + +void mal_pcm_deinterleave_s16(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_deinterleave_s16__reference(dst, src, frameCount, channels); +#else + mal_pcm_deinterleave_s16__optimized(dst, src, frameCount, channels); +#endif +} + + +// s24 +void mal_pcm_s24_to_u8__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_uint8* dst_u8 = (mal_uint8*)dst; + const mal_uint8* src_s24 = (const mal_uint8*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + mal_int8 x = (mal_int8)src_s24[i*3 + 2] + 128; + dst_u8[i] = (mal_uint8)x; + } +} + +void mal_pcm_s24_to_u8__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s24_to_u8__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_s24_to_u8__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s24_to_u8__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_s24_to_u8(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_s24_to_u8__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_s24_to_u8__sse(dst, src, count, ditherMode); +#else + mal_pcm_s24_to_u8__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_s24_to_s16__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_int16* dst_s16 = (mal_int16*)dst; + const mal_uint8* src_s24 = (const mal_uint8*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + mal_uint16 dst_lo = ((mal_uint16)src_s24[i*3 + 1]); + mal_uint16 dst_hi = ((mal_uint16)src_s24[i*3 + 2]) << 8; + dst_s16[i] = (mal_int16)dst_lo | dst_hi; + } +} + +void mal_pcm_s24_to_s16__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s24_to_s16__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_s24_to_s16__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s24_to_s16__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_s24_to_s16(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_s24_to_s16__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_s24_to_s16__sse(dst, src, count, ditherMode); +#else + mal_pcm_s24_to_s16__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_s24_to_s24(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_copy_memory_64(dst, src, count * 3); +} + + +void mal_pcm_s24_to_s32__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_int32* dst_s32 = (mal_int32*)dst; + const mal_uint8* src_s24 = (const mal_uint8*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + dst_s32[i] = (mal_int32)(((mal_uint32)(src_s24[i*3+0]) << 8) | ((mal_uint32)(src_s24[i*3+1]) << 16) | ((mal_uint32)(src_s24[i*3+2])) << 24); + } +} + +void mal_pcm_s24_to_s32__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s24_to_s32__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_s24_to_s32__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s24_to_s32__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_s24_to_s32(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_s24_to_s32__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_s24_to_s32__sse(dst, src, count, ditherMode); +#else + mal_pcm_s24_to_s32__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_s24_to_f32__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + float* dst_f32 = (float*)dst; + const mal_uint8* src_s24 = (const mal_uint8*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + float x = (float)(((mal_int32)(((mal_uint32)(src_s24[i*3+0]) << 8) | ((mal_uint32)(src_s24[i*3+1]) << 16) | ((mal_uint32)(src_s24[i*3+2])) << 24)) >> 8); + +#if 0 + // The accurate way. + x = x + 8388608.0f; // -8388608..8388607 to 0..16777215 + x = x * 0.00000011920929665621f; // 0..16777215 to 0..2 + x = x - 1; // 0..2 to -1..1 +#else + // The fast way. + x = x * 0.00000011920928955078125f; // -8388608..8388607 to -1..0.999969482421875 +#endif + + dst_f32[i] = x; + } +} + +void mal_pcm_s24_to_f32__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s24_to_f32__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_s24_to_f32__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s24_to_f32__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_s24_to_f32(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_s24_to_f32__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_s24_to_f32__sse(dst, src, count, ditherMode); +#else + mal_pcm_s24_to_f32__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_interleave_s24__reference(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_uint8* dst8 = (mal_uint8*)dst; + const mal_uint8** src8 = (const mal_uint8**)src; + + mal_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + mal_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst8[iFrame*3*channels + iChannel*3 + 0] = src8[iChannel][iFrame*3 + 0]; + dst8[iFrame*3*channels + iChannel*3 + 1] = src8[iChannel][iFrame*3 + 1]; + dst8[iFrame*3*channels + iChannel*3 + 2] = src8[iChannel][iFrame*3 + 2]; + } + } +} + +void mal_pcm_interleave_s24__optimized(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_pcm_interleave_s24__reference(dst, src, frameCount, channels); +} + +void mal_pcm_interleave_s24(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_interleave_s24__reference(dst, src, frameCount, channels); +#else + mal_pcm_interleave_s24__optimized(dst, src, frameCount, channels); +#endif +} + + +void mal_pcm_deinterleave_s24__reference(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_uint8** dst8 = (mal_uint8**)dst; + const mal_uint8* src8 = (const mal_uint8*)src; + + mal_uint32 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + mal_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst8[iChannel][iFrame*3 + 0] = src8[iFrame*3*channels + iChannel*3 + 0]; + dst8[iChannel][iFrame*3 + 1] = src8[iFrame*3*channels + iChannel*3 + 1]; + dst8[iChannel][iFrame*3 + 2] = src8[iFrame*3*channels + iChannel*3 + 2]; + } + } +} + +void mal_pcm_deinterleave_s24__optimized(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_pcm_deinterleave_s24__reference(dst, src, frameCount, channels); +} + +void mal_pcm_deinterleave_s24(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_deinterleave_s24__reference(dst, src, frameCount, channels); +#else + mal_pcm_deinterleave_s24__optimized(dst, src, frameCount, channels); +#endif +} + + + +// s32 +void mal_pcm_s32_to_u8__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_uint8* dst_u8 = (mal_uint8*)dst; + const mal_int32* src_s32 = (const mal_int32*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + mal_int32 x = src_s32[i]; + x = x >> 24; + x = x + 128; + dst_u8[i] = (mal_uint8)x; + } +} + +void mal_pcm_s32_to_u8__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s32_to_u8__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_s32_to_u8__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s32_to_u8__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_s32_to_u8(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_s32_to_u8__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_s32_to_u8__sse(dst, src, count, ditherMode); +#else + mal_pcm_s32_to_u8__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_s32_to_s16__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_int16* dst_s16 = (mal_int16*)dst; + const mal_int32* src_s32 = (const mal_int32*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + mal_int32 x = src_s32[i]; + x = x >> 16; + dst_s16[i] = (mal_int16)x; + } +} + +void mal_pcm_s32_to_s16__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s32_to_s16__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_s32_to_s16__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s32_to_s16__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_s32_to_s16(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_s32_to_s16__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_s32_to_s16__sse(dst, src, count, ditherMode); +#else + mal_pcm_s32_to_s16__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_s32_to_s24__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_uint8* dst_s24 = (mal_uint8*)dst; + const mal_int32* src_s32 = (const mal_int32*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + mal_uint32 x = (mal_uint32)src_s32[i]; + dst_s24[i*3+0] = (mal_uint8)((x & 0x0000FF00) >> 8); + dst_s24[i*3+1] = (mal_uint8)((x & 0x00FF0000) >> 16); + dst_s24[i*3+2] = (mal_uint8)((x & 0xFF000000) >> 24); + } +} + +void mal_pcm_s32_to_s24__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s32_to_s24__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_s32_to_s24__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s32_to_s24__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_s32_to_s24(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_s32_to_s24__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_s32_to_s24__sse(dst, src, count, ditherMode); +#else + mal_pcm_s32_to_s24__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_s32_to_s32(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_copy_memory_64(dst, src, count * sizeof(mal_int32)); +} + + +void mal_pcm_s32_to_f32__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + float* dst_f32 = (float*)dst; + const mal_int32* src_s32 = (const mal_int32*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + double x = src_s32[i]; + +#if 0 + x = x + 2147483648.0; + x = x * 0.0000000004656612873077392578125; + x = x - 1; +#else + x = x / 2147483648.0; +#endif + + dst_f32[i] = (float)x; + } +} + +void mal_pcm_s32_to_f32__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s32_to_f32__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_s32_to_f32__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_s32_to_f32__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_s32_to_f32(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_s32_to_f32__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_s32_to_f32__sse(dst, src, count, ditherMode); +#else + mal_pcm_s32_to_f32__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_interleave_s32__reference(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_int32* dst_s32 = (mal_int32*)dst; + const mal_int32** src_s32 = (const mal_int32**)src; + + mal_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + mal_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_s32[iFrame*channels + iChannel] = src_s32[iChannel][iFrame]; + } + } +} + +void mal_pcm_interleave_s32__optimized(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_pcm_interleave_s32__reference(dst, src, frameCount, channels); +} + +void mal_pcm_interleave_s32(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_interleave_s32__reference(dst, src, frameCount, channels); +#else + mal_pcm_interleave_s32__optimized(dst, src, frameCount, channels); +#endif +} + + +void mal_pcm_deinterleave_s32__reference(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_int32** dst_s32 = (mal_int32**)dst; + const mal_int32* src_s32 = (const mal_int32*)src; + + mal_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + mal_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_s32[iChannel][iFrame] = src_s32[iFrame*channels + iChannel]; + } + } +} + +void mal_pcm_deinterleave_s32__optimized(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_pcm_deinterleave_s32__reference(dst, src, frameCount, channels); +} + +void mal_pcm_deinterleave_s32(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_deinterleave_s32__reference(dst, src, frameCount, channels); +#else + mal_pcm_deinterleave_s32__optimized(dst, src, frameCount, channels); +#endif +} + + +// f32 +void mal_pcm_f32_to_u8__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_uint8* dst_u8 = (mal_uint8*)dst; + const float* src_f32 = (const float*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + float x = src_f32[i]; + x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); // clip + x = x + 1; // -1..1 to 0..2 + x = x * 127.5f; // 0..2 to 0..255 + + dst_u8[i] = (mal_uint8)x; + } +} + +void mal_pcm_f32_to_u8__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_f32_to_u8__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_f32_to_u8__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_f32_to_u8__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_f32_to_u8(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_f32_to_u8__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_f32_to_u8__sse(dst, src, count, ditherMode); +#else + mal_pcm_f32_to_u8__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_f32_to_s16__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_int16* dst_s16 = (mal_int16*)dst; + const float* src_f32 = (const float*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + float x = src_f32[i]; + x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); // clip + +#if 0 + // The accurate way. + x = x + 1; // -1..1 to 0..2 + x = x * 32767.5f; // 0..2 to 0..65535 + x = x - 32768.0f; // 0...65535 to -32768..32767 +#else + // The fast way. + x = x * 32767.0f; // -1..1 to -32767..32767 +#endif + + dst_s16[i] = (mal_int16)x; + } +} + +void mal_pcm_f32_to_s16__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_f32_to_s16__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_f32_to_s16__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_f32_to_s16__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_f32_to_s16(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_f32_to_s16__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_f32_to_s16__sse(dst, src, count, ditherMode); +#else + mal_pcm_f32_to_s16__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_f32_to_s24__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_uint8* dst_s24 = (mal_uint8*)dst; + const float* src_f32 = (const float*)src; + + mal_uint64 i; + for (i = 0; i < count; i += 1) { + float x = src_f32[i]; + x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); // clip + +#if 0 + // The accurate way. + x = x + 1; // -1..1 to 0..2 + x = x * 8388607.5f; // 0..2 to 0..16777215 + x = x - 8388608.0f; // 0..16777215 to -8388608..8388607 +#else + // The fast way. + x = x * 8388607.0f; // -1..1 to -8388607..8388607 +#endif + + mal_int32 r = (mal_int32)x; + dst_s24[(i*3)+0] = (mal_uint8)((r & 0x0000FF) >> 0); + dst_s24[(i*3)+1] = (mal_uint8)((r & 0x00FF00) >> 8); + dst_s24[(i*3)+2] = (mal_uint8)((r & 0xFF0000) >> 16); + } +} + +void mal_pcm_f32_to_s24__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_f32_to_s24__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_f32_to_s24__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_f32_to_s24__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_f32_to_s24(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_f32_to_s24__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_f32_to_s24__sse(dst, src, count, ditherMode); +#else + mal_pcm_f32_to_s24__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_f32_to_s32__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_int32* dst_s32 = (mal_int32*)dst; + const float* src_f32 = (const float*)src; + + mal_uint32 i; + for (i = 0; i < count; i += 1) { + double x = src_f32[i]; + x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); // clip + +#if 0 + // The accurate way. + x = x + 1; // -1..1 to 0..2 + x = x * 2147483647.5; // 0..2 to 0..4294967295 + x = x - 2147483648.0; // 0...4294967295 to -2147483648..2147483647 +#else + // The fast way. + x = x * 2147483647.0; // -1..1 to -2147483647..2147483647 +#endif + + dst_s32[i] = (mal_int32)x; + } +} + +void mal_pcm_f32_to_s32__optimized(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_f32_to_s32__reference(dst, src, count, ditherMode); +} + +#ifdef MAL_USE_SSE +void mal_pcm_f32_to_s32__sse(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + mal_pcm_f32_to_s32__optimized(dst, src, count, ditherMode); +} +#endif + +void mal_pcm_f32_to_s32(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_f32_to_s32__reference(dst, src, count, ditherMode); +#else +#ifdef MAL_USE_SSE + mal_pcm_f32_to_s32__sse(dst, src, count, ditherMode); +#else + mal_pcm_f32_to_s32__optimized(dst, src, count, ditherMode); +#endif +#endif +} + + +void mal_pcm_f32_to_f32(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode) +{ + (void)ditherMode; + + mal_copy_memory_64(dst, src, count * sizeof(float)); +} + + +void mal_pcm_interleave_f32__reference(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels) +{ + float* dst_f32 = (float*)dst; + const float** src_f32 = (const float**)src; + + mal_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + mal_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_f32[iFrame*channels + iChannel] = src_f32[iChannel][iFrame]; + } + } +} + +void mal_pcm_interleave_f32__optimized(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_pcm_interleave_f32__reference(dst, src, frameCount, channels); +} + +void mal_pcm_interleave_f32(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_interleave_f32__reference(dst, src, frameCount, channels); +#else + mal_pcm_interleave_f32__optimized(dst, src, frameCount, channels); +#endif +} + + +void mal_pcm_deinterleave_f32__reference(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels) +{ + float** dst_f32 = (float**)dst; + const float* src_f32 = (const float*)src; + + mal_uint64 iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + mal_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dst_f32[iChannel][iFrame] = src_f32[iFrame*channels + iChannel]; + } + } +} + +void mal_pcm_deinterleave_f32__optimized(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels) +{ + mal_pcm_deinterleave_f32__reference(dst, src, frameCount, channels); +} + +void mal_pcm_deinterleave_f32(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels) +{ +#ifdef MAL_USE_REFERENCE_CONVERSION_APIS + mal_pcm_deinterleave_f32__reference(dst, src, frameCount, channels); +#else + mal_pcm_deinterleave_f32__optimized(dst, src, frameCount, channels); +#endif +} + + + +mal_result mal_format_converter_init(const mal_format_converter_config* pConfig, mal_format_converter* pConverter) +{ + if (pConverter == NULL) { + return MAL_INVALID_ARGS; + } + mal_zero_object(pConverter); + + if (pConfig == NULL) { + return MAL_INVALID_ARGS; + } + + pConverter->config = *pConfig; + + switch (pConfig->formatIn) + { + case mal_format_u8: + { + if (pConfig->formatOut == mal_format_u8) { + pConverter->onConvertPCM = mal_pcm_u8_to_u8; + } else if (pConfig->formatOut == mal_format_s16) { + pConverter->onConvertPCM = mal_pcm_u8_to_s16; + } else if (pConfig->formatOut == mal_format_s24) { + pConverter->onConvertPCM = mal_pcm_u8_to_s24; + } else if (pConfig->formatOut == mal_format_s32) { + pConverter->onConvertPCM = mal_pcm_u8_to_s32; + } else if (pConfig->formatOut == mal_format_f32) { + pConverter->onConvertPCM = mal_pcm_u8_to_f32; + } + } break; + + case mal_format_s16: + { + if (pConfig->formatOut == mal_format_u8) { + pConverter->onConvertPCM = mal_pcm_s16_to_u8; + } else if (pConfig->formatOut == mal_format_s16) { + pConverter->onConvertPCM = mal_pcm_s16_to_s16; + } else if (pConfig->formatOut == mal_format_s24) { + pConverter->onConvertPCM = mal_pcm_s16_to_s24; + } else if (pConfig->formatOut == mal_format_s32) { + pConverter->onConvertPCM = mal_pcm_s16_to_s32; + } else if (pConfig->formatOut == mal_format_f32) { + pConverter->onConvertPCM = mal_pcm_s16_to_f32; + } + } break; + + case mal_format_s24: + { + if (pConfig->formatOut == mal_format_u8) { + pConverter->onConvertPCM = mal_pcm_s24_to_u8; + } else if (pConfig->formatOut == mal_format_s16) { + pConverter->onConvertPCM = mal_pcm_s24_to_s16; + } else if (pConfig->formatOut == mal_format_s24) { + pConverter->onConvertPCM = mal_pcm_s24_to_s24; + } else if (pConfig->formatOut == mal_format_s32) { + pConverter->onConvertPCM = mal_pcm_s24_to_s32; + } else if (pConfig->formatOut == mal_format_f32) { + pConverter->onConvertPCM = mal_pcm_s24_to_f32; + } + } break; + + case mal_format_s32: + { + if (pConfig->formatOut == mal_format_u8) { + pConverter->onConvertPCM = mal_pcm_s32_to_u8; + } else if (pConfig->formatOut == mal_format_s16) { + pConverter->onConvertPCM = mal_pcm_s32_to_s16; + } else if (pConfig->formatOut == mal_format_s24) { + pConverter->onConvertPCM = mal_pcm_s32_to_s24; + } else if (pConfig->formatOut == mal_format_s32) { + pConverter->onConvertPCM = mal_pcm_s32_to_s32; + } else if (pConfig->formatOut == mal_format_f32) { + pConverter->onConvertPCM = mal_pcm_s32_to_f32; + } + } break; + + case mal_format_f32: + default: + { + if (pConfig->formatOut == mal_format_u8) { + pConverter->onConvertPCM = mal_pcm_f32_to_u8; + } else if (pConfig->formatOut == mal_format_s16) { + pConverter->onConvertPCM = mal_pcm_f32_to_s16; + } else if (pConfig->formatOut == mal_format_s24) { + pConverter->onConvertPCM = mal_pcm_f32_to_s24; + } else if (pConfig->formatOut == mal_format_s32) { + pConverter->onConvertPCM = mal_pcm_f32_to_s32; + } else if (pConfig->formatOut == mal_format_f32) { + pConverter->onConvertPCM = mal_pcm_f32_to_f32; + } + } break; + } + + + switch (pConfig->formatOut) + { + case mal_format_u8: + { + pConverter->onInterleavePCM = mal_pcm_interleave_u8; + pConverter->onDeinterleavePCM = mal_pcm_deinterleave_u8; + } break; + case mal_format_s16: + { + pConverter->onInterleavePCM = mal_pcm_interleave_s16; + pConverter->onDeinterleavePCM = mal_pcm_deinterleave_s16; + } break; + case mal_format_s24: + { + pConverter->onInterleavePCM = mal_pcm_interleave_s24; + pConverter->onDeinterleavePCM = mal_pcm_deinterleave_s24; + } break; + case mal_format_s32: + { + pConverter->onInterleavePCM = mal_pcm_interleave_s32; + pConverter->onDeinterleavePCM = mal_pcm_deinterleave_s32; + } break; + case mal_format_f32: + default: + { + pConverter->onInterleavePCM = mal_pcm_interleave_f32; + pConverter->onDeinterleavePCM = mal_pcm_deinterleave_f32; + } break; + } + + return MAL_SUCCESS; +} + +mal_uint64 mal_format_converter_read(mal_format_converter* pConverter, mal_uint64 frameCount, void* pFramesOut, void* pUserData) +{ + if (pConverter == NULL || pFramesOut == NULL) { + return 0; + } + + mal_uint64 totalFramesRead = 0; + mal_uint32 sampleSizeIn = mal_get_bytes_per_sample(pConverter->config.formatIn); + mal_uint32 sampleSizeOut = mal_get_bytes_per_sample(pConverter->config.formatOut); + //mal_uint32 frameSizeIn = sampleSizeIn * pConverter->config.channels; + mal_uint32 frameSizeOut = sampleSizeOut * pConverter->config.channels; + mal_uint8* pNextFramesOut = (mal_uint8*)pFramesOut; + + if (pConverter->config.onRead != NULL) { + // Input data is interleaved. + if (pConverter->config.formatIn == pConverter->config.formatOut) { + // Pass through. + while (totalFramesRead < frameCount) { + mal_uint64 framesRemaining = (frameCount - totalFramesRead); + mal_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > 0xFFFFFFFF) { + framesToReadRightNow = 0xFFFFFFFF; + } + + mal_uint32 framesJustRead = (mal_uint32)pConverter->config.onRead(pConverter, (mal_uint32)framesToReadRightNow, pNextFramesOut, pUserData); + if (framesJustRead == 0) { + break; + } + + totalFramesRead += framesJustRead; + pNextFramesOut += framesJustRead * frameSizeOut; + } + } else { + // Conversion required. + MAL_ALIGN(MAL_SIMD_ALIGNMENT) mal_uint8 temp[MAL_MAX_CHANNELS * MAL_MAX_PCM_SAMPLE_SIZE_IN_BYTES * 128]; + mal_assert(sizeof(temp) <= 0xFFFFFFFF); + + mal_uint32 maxFramesToReadAtATime = sizeof(temp) / sampleSizeIn / pConverter->config.channels; + + while (totalFramesRead < frameCount) { + mal_uint64 framesRemaining = (frameCount - totalFramesRead); + mal_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > maxFramesToReadAtATime) { + framesToReadRightNow = maxFramesToReadAtATime; + } + + mal_uint32 framesJustRead = (mal_uint32)pConverter->config.onRead(pConverter, (mal_uint32)framesToReadRightNow, temp, pUserData); + if (framesJustRead == 0) { + break; + } + + pConverter->onConvertPCM(pNextFramesOut, temp, framesJustRead*pConverter->config.channels, pConverter->config.ditherMode); + + totalFramesRead += framesJustRead; + pNextFramesOut += framesJustRead * frameSizeOut; + } + } + } else { + // Input data is deinterleaved. If a conversion is required we need to do an intermediary step. + MAL_ALIGN(MAL_SIMD_ALIGNMENT) mal_uint8 tempSamplesOfOutFormat[MAL_MAX_CHANNELS * MAL_MAX_PCM_SAMPLE_SIZE_IN_BYTES * 128]; + mal_assert(sizeof(tempSamplesOfOutFormat) <= 0xFFFFFFFFF); + + void* ppTempSamplesOfOutFormat[MAL_MAX_CHANNELS]; + size_t splitBufferSizeOut; + mal_split_buffer(tempSamplesOfOutFormat, sizeof(tempSamplesOfOutFormat), pConverter->config.channels, MAL_SIMD_ALIGNMENT, (void**)&ppTempSamplesOfOutFormat, &splitBufferSizeOut); + + mal_uint32 maxFramesToReadAtATime = (mal_uint32)(splitBufferSizeOut / sampleSizeIn); + + while (totalFramesRead < frameCount) { + mal_uint64 framesRemaining = (frameCount - totalFramesRead); + mal_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > maxFramesToReadAtATime) { + framesToReadRightNow = maxFramesToReadAtATime; + } + + mal_uint32 framesJustRead = 0; + + if (pConverter->config.formatIn == pConverter->config.formatOut) { + // Only interleaving. + framesJustRead = (mal_uint32)pConverter->config.onReadDeinterleaved(pConverter, (mal_uint32)framesToReadRightNow, ppTempSamplesOfOutFormat, pUserData); + if (framesJustRead == 0) { + break; + } + } else { + // Interleaving + Conversion. Convert first, then interleave. + MAL_ALIGN(MAL_SIMD_ALIGNMENT) mal_uint8 tempSamplesOfInFormat[MAL_MAX_CHANNELS * MAL_MAX_PCM_SAMPLE_SIZE_IN_BYTES * 128]; + + void* ppTempSamplesOfInFormat[MAL_MAX_CHANNELS]; + size_t splitBufferSizeIn; + mal_split_buffer(tempSamplesOfInFormat, sizeof(tempSamplesOfInFormat), pConverter->config.channels, MAL_SIMD_ALIGNMENT, (void**)&ppTempSamplesOfInFormat, &splitBufferSizeIn); + + if (framesToReadRightNow > (splitBufferSizeIn / sampleSizeIn)) { + framesToReadRightNow = (splitBufferSizeIn / sampleSizeIn); + } + + framesJustRead = (mal_uint32)pConverter->config.onReadDeinterleaved(pConverter, (mal_uint32)framesToReadRightNow, ppTempSamplesOfInFormat, pUserData); + if (framesJustRead == 0) { + break; + } + + for (mal_uint32 iChannel = 0; iChannel < pConverter->config.channels; iChannel += 1) { + pConverter->onConvertPCM(ppTempSamplesOfOutFormat[iChannel], ppTempSamplesOfInFormat[iChannel], framesJustRead, pConverter->config.ditherMode); + } + } + + pConverter->onInterleavePCM(pNextFramesOut, (const void**)ppTempSamplesOfOutFormat, framesJustRead, pConverter->config.channels); + + totalFramesRead += framesJustRead; + pNextFramesOut += framesJustRead * frameSizeOut; + } + } + + return totalFramesRead; +} + +mal_uint64 mal_format_converter_read_deinterleaved(mal_format_converter* pConverter, mal_uint64 frameCount, void** ppSamplesOut, void* pUserData) +{ + if (pConverter == NULL || ppSamplesOut == NULL) { + return 0; + } + + mal_uint64 totalFramesRead = 0; + mal_uint32 sampleSizeIn = mal_get_bytes_per_sample(pConverter->config.formatIn); + mal_uint32 sampleSizeOut = mal_get_bytes_per_sample(pConverter->config.formatOut); + + mal_uint8* ppNextSamplesOut[MAL_MAX_CHANNELS]; + mal_copy_memory(ppNextSamplesOut, ppSamplesOut, sizeof(void*) * pConverter->config.channels); + + if (pConverter->config.onRead != NULL) { + // Input data is interleaved. + MAL_ALIGN(MAL_SIMD_ALIGNMENT) mal_uint8 tempSamplesOfOutFormat[MAL_MAX_CHANNELS * MAL_MAX_PCM_SAMPLE_SIZE_IN_BYTES * 128]; + mal_assert(sizeof(tempSamplesOfOutFormat) <= 0xFFFFFFFF); + + mal_uint32 maxFramesToReadAtATime = sizeof(tempSamplesOfOutFormat) / sampleSizeIn / pConverter->config.channels; + + while (totalFramesRead < frameCount) { + mal_uint64 framesRemaining = (frameCount - totalFramesRead); + mal_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > maxFramesToReadAtATime) { + framesToReadRightNow = maxFramesToReadAtATime; + } + + mal_uint32 framesJustRead = 0; + + if (pConverter->config.formatIn == pConverter->config.formatOut) { + // Only de-interleaving. + framesJustRead = (mal_uint32)pConverter->config.onRead(pConverter, (mal_uint32)framesToReadRightNow, tempSamplesOfOutFormat, pUserData); + if (framesJustRead == 0) { + break; + } + } else { + // De-interleaving + Conversion. Convert first, then de-interleave. + MAL_ALIGN(MAL_SIMD_ALIGNMENT) mal_uint8 tempSamplesOfInFormat[sizeof(tempSamplesOfOutFormat)]; + + framesJustRead = (mal_uint32)pConverter->config.onRead(pConverter, (mal_uint32)framesToReadRightNow, tempSamplesOfInFormat, pUserData); + if (framesJustRead == 0) { + break; + } + + pConverter->onConvertPCM(tempSamplesOfOutFormat, tempSamplesOfInFormat, framesJustRead * pConverter->config.channels, pConverter->config.ditherMode); + } + + pConverter->onDeinterleavePCM((void**)ppNextSamplesOut, tempSamplesOfOutFormat, framesJustRead, pConverter->config.channels); + + totalFramesRead += framesJustRead; + for (mal_uint32 iChannel = 0; iChannel < pConverter->config.channels; ++iChannel) { + ppNextSamplesOut[iChannel] += framesJustRead * sampleSizeOut; + } + } + } else { + // Input data is deinterleaved. + if (pConverter->config.formatIn == pConverter->config.formatOut) { + // Pass through. + while (totalFramesRead < frameCount) { + mal_uint64 framesRemaining = (frameCount - totalFramesRead); + mal_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > 0xFFFFFFFF) { + framesToReadRightNow = 0xFFFFFFFF; + } + + mal_uint32 framesJustRead = (mal_uint32)pConverter->config.onReadDeinterleaved(pConverter, (mal_uint32)framesToReadRightNow, (void**)ppNextSamplesOut, pUserData); + if (framesJustRead == 0) { + break; + } + + totalFramesRead += framesJustRead; + for (mal_uint32 iChannel = 0; iChannel < pConverter->config.channels; ++iChannel) { + ppNextSamplesOut[iChannel] += framesJustRead * sampleSizeOut; + } + } + } else { + // Conversion required. + MAL_ALIGN(MAL_SIMD_ALIGNMENT) mal_uint8 temp[MAL_MAX_CHANNELS][MAL_MAX_PCM_SAMPLE_SIZE_IN_BYTES * 128]; + mal_assert(sizeof(temp) <= 0xFFFFFFFF); + + void* ppTemp[MAL_MAX_CHANNELS]; + size_t splitBufferSize; + mal_split_buffer(temp, sizeof(temp), pConverter->config.channels, MAL_SIMD_ALIGNMENT, (void**)&ppTemp, &splitBufferSize); + + mal_uint32 maxFramesToReadAtATime = (mal_uint32)(splitBufferSize / sampleSizeIn); + + while (totalFramesRead < frameCount) { + mal_uint64 framesRemaining = (frameCount - totalFramesRead); + mal_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > maxFramesToReadAtATime) { + framesToReadRightNow = maxFramesToReadAtATime; + } + + mal_uint32 framesJustRead = (mal_uint32)pConverter->config.onReadDeinterleaved(pConverter, (mal_uint32)framesToReadRightNow, ppTemp, pUserData); + if (framesJustRead == 0) { + break; + } + + for (mal_uint32 iChannel = 0; iChannel < pConverter->config.channels; iChannel += 1) { + pConverter->onConvertPCM(ppNextSamplesOut[iChannel], ppTemp[iChannel], framesJustRead, pConverter->config.ditherMode); + ppNextSamplesOut[iChannel] += framesJustRead * sampleSizeOut; + } + + totalFramesRead += framesJustRead; + } + } + } + + return totalFramesRead; +} + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Channel Routing +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// -X = Left, +X = Right +// -Y = Bottom, +Y = Top +// -Z = Front, +Z = Back +typedef struct +{ + float x; + float y; + float z; +} mal_vec3; + +static MAL_INLINE mal_vec3 mal_vec3f(float x, float y, float z) +{ + mal_vec3 r; + r.x = x; + r.y = y; + r.z = z; + + return r; +} + +static MAL_INLINE mal_vec3 mal_vec3_add(mal_vec3 a, mal_vec3 b) +{ + return mal_vec3f( + a.x + b.x, + a.y + b.y, + a.z + b.z + ); +} + +static MAL_INLINE mal_vec3 mal_vec3_sub(mal_vec3 a, mal_vec3 b) +{ + return mal_vec3f( + a.x - b.x, + a.y - b.y, + a.z - b.z + ); +} + +static MAL_INLINE mal_vec3 mal_vec3_mul(mal_vec3 a, mal_vec3 b) +{ + return mal_vec3f( + a.x * b.x, + a.y * b.y, + a.z * b.z + ); +} + +static MAL_INLINE mal_vec3 mal_vec3_div(mal_vec3 a, mal_vec3 b) +{ + return mal_vec3f( + a.x / b.x, + a.y / b.y, + a.z / b.z + ); +} + +static MAL_INLINE float mal_vec3_dot(mal_vec3 a, mal_vec3 b) +{ + return a.x*b.x + a.y*b.y + a.z*b.z; +} + +static MAL_INLINE float mal_vec3_length2(mal_vec3 a) +{ + return mal_vec3_dot(a, a); +} + +static MAL_INLINE float mal_vec3_length(mal_vec3 a) +{ + return (float)sqrt(mal_vec3_length2(a)); +} + +static MAL_INLINE mal_vec3 mal_vec3_normalize(mal_vec3 a) +{ + float len = 1 / mal_vec3_length(a); + + mal_vec3 r; + r.x = a.x * len; + r.y = a.y * len; + r.z = a.z * len; + + return r; +} + +static MAL_INLINE float mal_vec3_distance(mal_vec3 a, mal_vec3 b) +{ + return mal_vec3_length(mal_vec3_sub(a, b)); +} + + +#define MAL_PLANE_LEFT 0 +#define MAL_PLANE_RIGHT 1 +#define MAL_PLANE_FRONT 2 +#define MAL_PLANE_BACK 3 +#define MAL_PLANE_BOTTOM 4 +#define MAL_PLANE_TOP 5 + +float g_malChannelPlaneRatios[MAL_CHANNEL_POSITION_COUNT][6] = { + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_NONE + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_MONO + { 0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_FRONT_LEFT + { 0.0f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_FRONT_RIGHT + { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_FRONT_CENTER + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_LFE + { 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f}, // MAL_CHANNEL_BACK_LEFT + { 0.0f, 0.5f, 0.0f, 0.0f, 0.5f, 0.0f}, // MAL_CHANNEL_BACK_RIGHT + { 0.25f, 0.0f, 0.75f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_FRONT_LEFT_CENTER + { 0.0f, 0.25f, 0.75f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_FRONT_RIGHT_CENTER + { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}, // MAL_CHANNEL_BACK_CENTER + { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_SIDE_LEFT + { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_SIDE_RIGHT + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, // MAL_CHANNEL_TOP_CENTER + { 0.33f, 0.0f, 0.33f, 0.0f, 0.0f, 0.34f}, // MAL_CHANNEL_TOP_FRONT_LEFT + { 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.5f}, // MAL_CHANNEL_TOP_FRONT_CENTER + { 0.0f, 0.33f, 0.33f, 0.0f, 0.0f, 0.34f}, // MAL_CHANNEL_TOP_FRONT_RIGHT + { 0.33f, 0.0f, 0.0f, 0.33f, 0.0f, 0.34f}, // MAL_CHANNEL_TOP_BACK_LEFT + { 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f}, // MAL_CHANNEL_TOP_BACK_CENTER + { 0.0f, 0.33f, 0.0f, 0.33f, 0.0f, 0.34f}, // MAL_CHANNEL_TOP_BACK_RIGHT + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_0 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_1 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_2 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_3 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_4 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_5 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_6 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_7 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_8 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_9 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_10 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_11 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_12 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_13 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_14 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_15 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_16 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_17 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_18 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_19 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_20 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_21 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_22 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_23 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_24 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_25 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_26 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_27 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_28 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_29 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_30 + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_AUX_31 +}; + +float mal_calculate_channel_position_planar_weight(mal_channel channelPositionA, mal_channel channelPositionB) +{ + // Imagine the following simplified example: You have a single input speaker which is the front/left speaker which you want to convert to + // the following output configuration: + // + // - front/left + // - side/left + // - back/left + // + // The front/left output is easy - it the same speaker position so it receives the full contribution of the front/left input. The amount + // of contribution to apply to the side/left and back/left speakers, however, is a bit more complicated. + // + // Imagine the front/left speaker as emitting audio from two planes - the front plane and the left plane. You can think of the front/left + // speaker emitting half of it's total volume from the front, and the other half from the left. Since part of it's volume is being emitted + // from the left side, and the side/left and back/left channels also emit audio from the left plane, one would expect that they would + // receive some amount of contribution from front/left speaker. The amount of contribution depends on how many planes are shared between + // the two speakers. Note that in the examples below I've added a top/front/left speaker as an example just to show how the math works + // across 3 spatial dimensions. + // + // The first thing to do is figure out how each speaker's volume is spread over each of plane: + // - front/left: 2 planes (front and left) = 1/2 = half it's total volume on each plane + // - side/left: 1 plane (left only) = 1/1 = entire volume from left plane + // - back/left: 2 planes (back and left) = 1/2 = half it's total volume on each plane + // - top/front/left: 3 planes (top, front and left) = 1/3 = one third it's total volume on each plane + // + // The amount of volume each channel contributes to each of it's planes is what controls how much it is willing to given and take to other + // channels on the same plane. The volume that is willing to the given by one channel is multiplied by the volume that is willing to be + // taken by the other to produce the final contribution. + + // Contribution = Sum(Volume to Give * Volume to Take) + float contribution = + g_malChannelPlaneRatios[channelPositionA][0] * g_malChannelPlaneRatios[channelPositionB][0] + + g_malChannelPlaneRatios[channelPositionA][1] * g_malChannelPlaneRatios[channelPositionB][1] + + g_malChannelPlaneRatios[channelPositionA][2] * g_malChannelPlaneRatios[channelPositionB][2] + + g_malChannelPlaneRatios[channelPositionA][3] * g_malChannelPlaneRatios[channelPositionB][3] + + g_malChannelPlaneRatios[channelPositionA][4] * g_malChannelPlaneRatios[channelPositionB][4] + + g_malChannelPlaneRatios[channelPositionA][5] * g_malChannelPlaneRatios[channelPositionB][5]; + + return contribution; +} + +float mal_channel_router__calculate_input_channel_planar_weight(const mal_channel_router* pRouter, mal_channel channelPositionIn, mal_channel channelPositionOut) +{ + mal_assert(pRouter != NULL); + (void)pRouter; + + return mal_calculate_channel_position_planar_weight(channelPositionIn, channelPositionOut); +} + +mal_bool32 mal_channel_router__is_spatial_channel_position(const mal_channel_router* pRouter, mal_channel channelPosition) +{ + mal_assert(pRouter != NULL); + (void)pRouter; + + if (channelPosition == MAL_CHANNEL_NONE || channelPosition == MAL_CHANNEL_MONO || channelPosition == MAL_CHANNEL_LFE) { + return MAL_FALSE; + } + + for (int i = 0; i < 6; ++i) { + if (g_malChannelPlaneRatios[channelPosition][i] != 0) { + return MAL_TRUE; + } + } + + return MAL_FALSE; +} + +mal_result mal_channel_router_init(const mal_channel_router_config* pConfig, mal_channel_router* pRouter) +{ + if (pRouter == NULL) { + return MAL_INVALID_ARGS; + } + + mal_zero_object(pRouter); + + if (pConfig == NULL) { + return MAL_INVALID_ARGS; + } + if (pConfig->onReadDeinterleaved == NULL) { + return MAL_INVALID_ARGS; + } + + if (!mal_channel_map_valid(pConfig->channelsIn, pConfig->channelMapIn)) { + return MAL_INVALID_ARGS; // Invalid input channel map. + } + if (!mal_channel_map_valid(pConfig->channelsOut, pConfig->channelMapOut)) { + return MAL_INVALID_ARGS; // Invalid output channel map. + } + + pRouter->config = *pConfig; + + // SIMD + pRouter->useSSE2 = mal_has_sse2() && !pConfig->noSSE2; + pRouter->useAVX = mal_has_avx() && !pConfig->noAVX; + pRouter->useAVX512 = mal_has_avx512f() && !pConfig->noAVX512; + pRouter->useNEON = mal_has_neon() && !pConfig->noNEON; + + // If the input and output channels and channel maps are the same we should use a passthrough. + if (pRouter->config.channelsIn == pRouter->config.channelsOut) { + if (mal_channel_map_equal(pRouter->config.channelsIn, pRouter->config.channelMapIn, pRouter->config.channelMapOut)) { + pRouter->isPassthrough = MAL_TRUE; + } + if (mal_channel_map_blank(pRouter->config.channelsIn, pRouter->config.channelMapIn) || mal_channel_map_blank(pRouter->config.channelsOut, pRouter->config.channelMapOut)) { + pRouter->isPassthrough = MAL_TRUE; + } + } + + // Here is where we do a bit of pre-processing to know how each channel should be combined to make up the output. Rules: + // + // 1) If it's a passthrough, do nothing - it's just a simple memcpy(). + // 2) If the channel counts are the same and every channel position in the input map is present in the output map, use a + // simple shuffle. An example might be different 5.1 channel layouts. + // 3) Otherwise channels are blended based on spatial locality. + if (!pRouter->isPassthrough) { + if (pRouter->config.channelsIn == pRouter->config.channelsOut) { + mal_bool32 areAllChannelPositionsPresent = MAL_TRUE; + for (mal_uint32 iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + mal_bool32 isInputChannelPositionInOutput = MAL_FALSE; + for (mal_uint32 iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + if (pRouter->config.channelMapIn[iChannelIn] == pRouter->config.channelMapOut[iChannelOut]) { + isInputChannelPositionInOutput = MAL_TRUE; + break; + } + } + + if (!isInputChannelPositionInOutput) { + areAllChannelPositionsPresent = MAL_FALSE; + break; + } + } + + if (areAllChannelPositionsPresent) { + pRouter->isSimpleShuffle = MAL_TRUE; + + // All the router will be doing is rearranging channels which means all we need to do is use a shuffling table which is just + // a mapping between the index of the input channel to the index of the output channel. + for (mal_uint32 iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + for (mal_uint32 iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + if (pRouter->config.channelMapIn[iChannelIn] == pRouter->config.channelMapOut[iChannelOut]) { + pRouter->shuffleTable[iChannelIn] = (mal_uint8)iChannelOut; + break; + } + } + } + } + } + } + + + // Here is where weights are calculated. Note that we calculate the weights at all times, even when using a passthrough and simple + // shuffling. We use different algorithms for calculating weights depending on our mixing mode. + // + // In simple mode we don't do any blending (except for converting between mono, which is done in a later step). Instead we just + // map 1:1 matching channels. In this mode, if no channels in the input channel map correspond to anything in the output channel + // map, nothing will be heard! + + // In all cases we need to make sure all channels that are present in both channel maps have a 1:1 mapping. + for (mal_uint32 iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + mal_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn]; + + for (mal_uint32 iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + mal_channel channelPosOut = pRouter->config.channelMapOut[iChannelOut]; + + if (channelPosIn == channelPosOut) { + pRouter->weights[iChannelIn][iChannelOut] = 1; + } + } + } + + // The mono channel is accumulated on all other channels, except LFE. Make sure in this loop we exclude output mono channels since + // they were handled in the pass above. + for (mal_uint32 iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + mal_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn]; + + if (channelPosIn == MAL_CHANNEL_MONO) { + for (mal_uint32 iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + mal_channel channelPosOut = pRouter->config.channelMapOut[iChannelOut]; + + if (channelPosOut != MAL_CHANNEL_NONE && channelPosOut != MAL_CHANNEL_MONO && channelPosOut != MAL_CHANNEL_LFE) { + pRouter->weights[iChannelIn][iChannelOut] = 1; + } + } + } + } + + // The output mono channel is the average of all non-none, non-mono and non-lfe input channels. + { + mal_uint32 len = 0; + for (mal_uint32 iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + mal_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn]; + + if (channelPosIn != MAL_CHANNEL_NONE && channelPosIn != MAL_CHANNEL_MONO && channelPosIn != MAL_CHANNEL_LFE) { + len += 1; + } + } + + if (len > 0) { + float monoWeight = 1.0f / len; + + for (mal_uint32 iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + mal_channel channelPosOut = pRouter->config.channelMapOut[iChannelOut]; + + if (channelPosOut == MAL_CHANNEL_MONO) { + for (mal_uint32 iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + mal_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn]; + + if (channelPosIn != MAL_CHANNEL_NONE && channelPosIn != MAL_CHANNEL_MONO && channelPosIn != MAL_CHANNEL_LFE) { + pRouter->weights[iChannelIn][iChannelOut] += monoWeight; + } + } + } + } + } + } + + + // Input and output channels that are not present on the other side need to be blended in based on spatial locality. + if (pRouter->config.mixingMode != mal_channel_mix_mode_simple) { + // Unmapped input channels. + for (mal_uint32 iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + mal_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn]; + + if (mal_channel_router__is_spatial_channel_position(pRouter, channelPosIn)) { + if (!mal_channel_map_contains_channel_position(pRouter->config.channelsOut, pRouter->config.channelMapOut, channelPosIn)) { + for (mal_uint32 iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + mal_channel channelPosOut = pRouter->config.channelMapOut[iChannelOut]; + + if (mal_channel_router__is_spatial_channel_position(pRouter, channelPosOut)) { + float weight = 0; + if (pRouter->config.mixingMode == mal_channel_mix_mode_planar_blend) { + weight = mal_channel_router__calculate_input_channel_planar_weight(pRouter, channelPosIn, channelPosOut); + } + + // Only apply the weight if we haven't already got some contribution from the respective channels. + if (pRouter->weights[iChannelIn][iChannelOut] == 0) { + pRouter->weights[iChannelIn][iChannelOut] = weight; + } + } + } + } + } + } + + // Unmapped output channels. + for (mal_uint32 iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + mal_channel channelPosOut = pRouter->config.channelMapOut[iChannelOut]; + + if (mal_channel_router__is_spatial_channel_position(pRouter, channelPosOut)) { + if (!mal_channel_map_contains_channel_position(pRouter->config.channelsIn, pRouter->config.channelMapIn, channelPosOut)) { + for (mal_uint32 iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + mal_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn]; + + if (mal_channel_router__is_spatial_channel_position(pRouter, channelPosIn)) { + float weight = 0; + if (pRouter->config.mixingMode == mal_channel_mix_mode_planar_blend) { + weight = mal_channel_router__calculate_input_channel_planar_weight(pRouter, channelPosIn, channelPosOut); + } + + // Only apply the weight if we haven't already got some contribution from the respective channels. + if (pRouter->weights[iChannelIn][iChannelOut] == 0) { + pRouter->weights[iChannelIn][iChannelOut] = weight; + } + } + } + } + } + } + } + + return MAL_SUCCESS; +} + +static MAL_INLINE mal_bool32 mal_channel_router__can_use_sse2(mal_channel_router* pRouter, const float* pSamplesOut, const float* pSamplesIn) +{ + return pRouter->useSSE2 && (((mal_uintptr)pSamplesOut & 15) == 0) && (((mal_uintptr)pSamplesIn & 15) == 0); +} + +static MAL_INLINE mal_bool32 mal_channel_router__can_use_avx(mal_channel_router* pRouter, const float* pSamplesOut, const float* pSamplesIn) +{ + return pRouter->useAVX && (((mal_uintptr)pSamplesOut & 31) == 0) && (((mal_uintptr)pSamplesIn & 31) == 0); +} + +static MAL_INLINE mal_bool32 mal_channel_router__can_use_avx512(mal_channel_router* pRouter, const float* pSamplesOut, const float* pSamplesIn) +{ + return pRouter->useAVX512 && (((mal_uintptr)pSamplesOut & 63) == 0) && (((mal_uintptr)pSamplesIn & 63) == 0); +} + +static MAL_INLINE mal_bool32 mal_channel_router__can_use_neon(mal_channel_router* pRouter, const float* pSamplesOut, const float* pSamplesIn) +{ + return pRouter->useNEON && (((mal_uintptr)pSamplesOut & 15) == 0) && (((mal_uintptr)pSamplesIn & 15) == 0); +} + +void mal_channel_router__do_routing(mal_channel_router* pRouter, mal_uint64 frameCount, float** ppSamplesOut, const float** ppSamplesIn) +{ + mal_assert(pRouter != NULL); + mal_assert(pRouter->isPassthrough == MAL_FALSE); + + if (pRouter->isSimpleShuffle) { + // A shuffle is just a re-arrangement of channels and does not require any arithmetic. + mal_assert(pRouter->config.channelsIn == pRouter->config.channelsOut); + for (mal_uint32 iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + mal_uint32 iChannelOut = pRouter->shuffleTable[iChannelIn]; + mal_copy_memory_64(ppSamplesOut[iChannelOut], ppSamplesIn[iChannelIn], frameCount * sizeof(float)); + } + } else { + // This is the more complicated case. Each of the output channels is accumulated with 0 or more input channels. + + // Clear. + for (mal_uint32 iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + mal_zero_memory_64(ppSamplesOut[iChannelOut], frameCount * sizeof(float)); + } + + // Accumulate. + for (mal_uint32 iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) { + for (mal_uint32 iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) { + mal_uint64 iFrame = 0; + +#if defined(MAL_SUPPORT_AVX512) + if (mal_channel_router__can_use_avx512(pRouter, ppSamplesOut[iChannelOut], ppSamplesIn[iChannelIn])) { + __m512 weight = _mm512_set1_ps(pRouter->weights[iChannelIn][iChannelOut]); + + mal_uint64 frameCount16 = frameCount/16; + for (mal_uint64 iFrame16 = 0; iFrame16 < frameCount16; iFrame16 += 1) { + __m512* pO = (__m512*)ppSamplesOut[iChannelOut] + iFrame16; + __m512* pI = (__m512*)ppSamplesIn [iChannelIn ] + iFrame16; + *pO = _mm512_add_ps(*pO, _mm512_mul_ps(*pI, weight)); + } + + iFrame += frameCount16*16; + } + else +#endif +#if defined(MAL_SUPPORT_AVX) + if (mal_channel_router__can_use_avx(pRouter, ppSamplesOut[iChannelOut], ppSamplesIn[iChannelIn])) { + __m256 weight = _mm256_set1_ps(pRouter->weights[iChannelIn][iChannelOut]); + + mal_uint64 frameCount8 = frameCount/8; + for (mal_uint64 iFrame8 = 0; iFrame8 < frameCount8; iFrame8 += 1) { + __m256* pO = (__m256*)ppSamplesOut[iChannelOut] + iFrame8; + __m256* pI = (__m256*)ppSamplesIn [iChannelIn ] + iFrame8; + *pO = _mm256_add_ps(*pO, _mm256_mul_ps(*pI, weight)); + } + + iFrame += frameCount8*8; + } + else +#endif +#if defined(MAL_SUPPORT_SSE2) + if (mal_channel_router__can_use_sse2(pRouter, ppSamplesOut[iChannelOut], ppSamplesIn[iChannelIn])) { + __m128 weight = _mm_set1_ps(pRouter->weights[iChannelIn][iChannelOut]); + + mal_uint64 frameCount4 = frameCount/4; + for (mal_uint64 iFrame4 = 0; iFrame4 < frameCount4; iFrame4 += 1) { + __m128* pO = (__m128*)ppSamplesOut[iChannelOut] + iFrame4; + __m128* pI = (__m128*)ppSamplesIn [iChannelIn ] + iFrame4; + *pO = _mm_add_ps(*pO, _mm_mul_ps(*pI, weight)); + } + + iFrame += frameCount4*4; + } else +#endif + { // Reference. + float weight0 = pRouter->weights[iChannelIn][iChannelOut]; + float weight1 = pRouter->weights[iChannelIn][iChannelOut]; + float weight2 = pRouter->weights[iChannelIn][iChannelOut]; + float weight3 = pRouter->weights[iChannelIn][iChannelOut]; + + mal_uint64 frameCount4 = frameCount/4; + for (mal_uint64 iFrame4 = 0; iFrame4 < frameCount4; iFrame4 += 1) { + ppSamplesOut[iChannelOut][iFrame+0] += ppSamplesIn[iChannelIn][iFrame+0] * weight0; + ppSamplesOut[iChannelOut][iFrame+1] += ppSamplesIn[iChannelIn][iFrame+1] * weight1; + ppSamplesOut[iChannelOut][iFrame+2] += ppSamplesIn[iChannelIn][iFrame+2] * weight2; + ppSamplesOut[iChannelOut][iFrame+3] += ppSamplesIn[iChannelIn][iFrame+3] * weight3; + iFrame += 4; + } + } + + // Leftover. + for (; iFrame < frameCount; ++iFrame) { + ppSamplesOut[iChannelOut][iFrame] += ppSamplesIn[iChannelIn][iFrame] * pRouter->weights[iChannelIn][iChannelOut]; + } + } + } + } +} + +mal_uint64 mal_channel_router_read_deinterleaved(mal_channel_router* pRouter, mal_uint64 frameCount, void** ppSamplesOut, void* pUserData) +{ + if (pRouter == NULL || ppSamplesOut == NULL) { + return 0; + } + + // Fast path for a passthrough. + if (pRouter->isPassthrough) { + if (frameCount <= 0xFFFFFFFF) { + return (mal_uint32)pRouter->config.onReadDeinterleaved(pRouter, (mal_uint32)frameCount, ppSamplesOut, pUserData); + } else { + float* ppNextSamplesOut[MAL_MAX_CHANNELS]; + mal_copy_memory(ppNextSamplesOut, ppSamplesOut, sizeof(float*) * pRouter->config.channelsOut); + + mal_uint64 totalFramesRead = 0; + while (totalFramesRead < frameCount) { + mal_uint64 framesRemaining = (frameCount - totalFramesRead); + mal_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > 0xFFFFFFFF) { + framesToReadRightNow = 0xFFFFFFFF; + } + + mal_uint32 framesJustRead = (mal_uint32)pRouter->config.onReadDeinterleaved(pRouter, (mal_uint32)framesToReadRightNow, (void**)ppNextSamplesOut, pUserData); + if (framesJustRead == 0) { + break; + } + + totalFramesRead += framesJustRead; + for (mal_uint32 iChannel = 0; iChannel < pRouter->config.channelsOut; ++iChannel) { + ppNextSamplesOut[iChannel] += framesJustRead; + } + } + } + } + + // Slower path for a non-passthrough. + float* ppNextSamplesOut[MAL_MAX_CHANNELS]; + mal_copy_memory(ppNextSamplesOut, ppSamplesOut, sizeof(float*) * pRouter->config.channelsOut); + + MAL_ALIGN(MAL_SIMD_ALIGNMENT) float temp[MAL_MAX_CHANNELS * 256]; + mal_assert(sizeof(temp) <= 0xFFFFFFFF); + + float* ppTemp[MAL_MAX_CHANNELS]; + size_t maxBytesToReadPerFrameEachIteration; + mal_split_buffer(temp, sizeof(temp), pRouter->config.channelsIn, MAL_SIMD_ALIGNMENT, (void**)&ppTemp, &maxBytesToReadPerFrameEachIteration); + + size_t maxFramesToReadEachIteration = maxBytesToReadPerFrameEachIteration/sizeof(float); + + mal_uint64 totalFramesRead = 0; + while (totalFramesRead < frameCount) { + mal_uint64 framesRemaining = (frameCount - totalFramesRead); + mal_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > maxFramesToReadEachIteration) { + framesToReadRightNow = maxFramesToReadEachIteration; + } + + mal_uint32 framesJustRead = pRouter->config.onReadDeinterleaved(pRouter, (mal_uint32)framesToReadRightNow, (void**)ppTemp, pUserData); + if (framesJustRead == 0) { + break; + } + + mal_channel_router__do_routing(pRouter, framesJustRead, (float**)ppNextSamplesOut, (const float**)ppTemp); // <-- Real work is done here. + + totalFramesRead += framesJustRead; + if (totalFramesRead < frameCount) { + for (mal_uint32 iChannel = 0; iChannel < pRouter->config.channelsIn; iChannel += 1) { + ppNextSamplesOut[iChannel] += framesJustRead; + } + } + } + + return totalFramesRead; +} + +mal_channel_router_config mal_channel_router_config_init(mal_uint32 channelsIn, const mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_uint32 channelsOut, const mal_channel channelMapOut[MAL_MAX_CHANNELS], mal_channel_mix_mode mixingMode, mal_channel_router_read_deinterleaved_proc onRead, void* pUserData) +{ + mal_channel_router_config config; + mal_zero_object(&config); + + config.channelsIn = channelsIn; + for (mal_uint32 iChannel = 0; iChannel < channelsIn; ++iChannel) { + config.channelMapIn[iChannel] = channelMapIn[iChannel]; + } + + config.channelsOut = channelsOut; + for (mal_uint32 iChannel = 0; iChannel < channelsOut; ++iChannel) { + config.channelMapOut[iChannel] = channelMapOut[iChannel]; + } + + config.mixingMode = mixingMode; + config.onReadDeinterleaved = onRead; + config.pUserData = pUserData; + + return config; +} + + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // SRC // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -void mal_src_cache_init(mal_src* pSRC, mal_src_cache* pCache) +mal_uint64 mal_calculate_frame_count_after_src(mal_uint32 sampleRateOut, mal_uint32 sampleRateIn, mal_uint64 frameCountIn) { - mal_assert(pSRC != NULL); - mal_assert(pCache != NULL); + double srcRatio = (double)sampleRateOut / sampleRateIn; + double frameCountOutF = frameCountIn * srcRatio; - pCache->pSRC = pSRC; - pCache->cachedFrameCount = 0; - pCache->iNextFrame = 0; -} + mal_uint64 frameCountOut = (mal_uint64)frameCountOutF; -mal_uint32 mal_src_cache_read_frames(mal_src_cache* pCache, mal_uint32 frameCount, float* pFramesOut) -{ - mal_assert(pCache != NULL); - mal_assert(pCache->pSRC != NULL); - mal_assert(pCache->pSRC->onRead != NULL); - mal_assert(frameCount > 0); - mal_assert(pFramesOut != NULL); - - mal_uint32 channels = pCache->pSRC->config.channels; - - mal_uint32 totalFramesRead = 0; - while (frameCount > 0) { - // If there's anything in memory go ahead and copy that over first. - mal_uint32 framesRemainingInMemory = pCache->cachedFrameCount - pCache->iNextFrame; - mal_uint32 framesToReadFromMemory = frameCount; - if (framesToReadFromMemory > framesRemainingInMemory) { - framesToReadFromMemory = framesRemainingInMemory; - } - - mal_copy_memory(pFramesOut, pCache->pCachedFrames + pCache->iNextFrame*channels, framesToReadFromMemory * channels * sizeof(float)); - pCache->iNextFrame += framesToReadFromMemory; - - totalFramesRead += framesToReadFromMemory; - frameCount -= framesToReadFromMemory; - if (frameCount == 0) { - break; - } - - - // At this point there are still more frames to read from the client, so we'll need to reload the cache with fresh data. - mal_assert(frameCount > 0); - pFramesOut += framesToReadFromMemory * channels; - - pCache->iNextFrame = 0; - pCache->cachedFrameCount = 0; - if (pCache->pSRC->config.formatIn == mal_format_f32) { - // No need for a conversion - read straight into the cache. - mal_uint32 framesToReadFromClient = mal_countof(pCache->pCachedFrames) / pCache->pSRC->config.channels; - if (framesToReadFromClient > pCache->pSRC->config.cacheSizeInFrames) { - framesToReadFromClient = pCache->pSRC->config.cacheSizeInFrames; - } - - pCache->cachedFrameCount = pCache->pSRC->onRead(pCache->pSRC, framesToReadFromClient, pCache->pCachedFrames, pCache->pSRC->pUserData); - } else { - // A format conversion is required which means we need to use an intermediary buffer. - mal_uint8 pIntermediaryBuffer[sizeof(pCache->pCachedFrames)]; - mal_uint32 framesToReadFromClient = mal_min(mal_buffer_frame_capacity(pIntermediaryBuffer, channels, pCache->pSRC->config.formatIn), mal_buffer_frame_capacity(pCache->pCachedFrames, channels, mal_format_f32)); - if (framesToReadFromClient > pCache->pSRC->config.cacheSizeInFrames) { - framesToReadFromClient = pCache->pSRC->config.cacheSizeInFrames; - } - - pCache->cachedFrameCount = pCache->pSRC->onRead(pCache->pSRC, framesToReadFromClient, pIntermediaryBuffer, pCache->pSRC->pUserData); - - // Convert to f32. - mal_pcm_convert(pCache->pCachedFrames, mal_format_f32, pIntermediaryBuffer, pCache->pSRC->config.formatIn, pCache->cachedFrameCount * channels); - } - - - // Get out of this loop if nothing was able to be retrieved. - if (pCache->cachedFrameCount == 0) { - break; - } + // If the output frame count is fractional, make sure we add an extra frame to ensure there's enough room for that last sample. + if ((frameCountOutF - frameCountOut) > 0.0) { + frameCountOut += 1; } - return totalFramesRead; + return frameCountOut; } -mal_uint32 mal_src_read_frames_passthrough(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush); -mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush); +mal_uint64 mal_src_read_deinterleaved__passthrough(mal_src* pSRC, mal_uint64 frameCount, void** ppSamplesOut, mal_bool32 flush, void* pUserData); +mal_uint64 mal_src_read_deinterleaved__linear(mal_src* pSRC, mal_uint64 frameCount, void** ppSamplesOut, mal_bool32 flush, void* pUserData); -mal_result mal_src_init(mal_src_config* pConfig, mal_src_read_proc onRead, void* pUserData, mal_src* pSRC) +mal_result mal_src_init(const mal_src_config* pConfig, mal_src* pSRC) { - if (pSRC == NULL) return MAL_INVALID_ARGS; + if (pSRC == NULL) { + return MAL_INVALID_ARGS; + } + mal_zero_object(pSRC); - if (pConfig == NULL || onRead == NULL) return MAL_INVALID_ARGS; - if (pConfig->channels == 0 || pConfig->channels > MAL_MAX_CHANNELS) return MAL_INVALID_ARGS; - - pSRC->config = *pConfig; - pSRC->onRead = onRead; - pSRC->pUserData = pUserData; - - if (pSRC->config.cacheSizeInFrames > MAL_SRC_CACHE_SIZE_IN_FRAMES || pSRC->config.cacheSizeInFrames == 0) { - pSRC->config.cacheSizeInFrames = MAL_SRC_CACHE_SIZE_IN_FRAMES; + if (pConfig == NULL || pConfig->onReadDeinterleaved == NULL) { + return MAL_INVALID_ARGS; + } + if (pConfig->channels == 0 || pConfig->channels > MAL_MAX_CHANNELS) { + return MAL_INVALID_ARGS; } - mal_src_cache_init(pSRC, &pSRC->cache); + pSRC->config = *pConfig; + + return MAL_SUCCESS; +} + +mal_result mal_src_set_input_sample_rate(mal_src* pSRC, mal_uint32 sampleRateIn) +{ + if (pSRC == NULL) { + return MAL_INVALID_ARGS; + } + + // Must have a sample rate of > 0. + if (sampleRateIn == 0) { + return MAL_INVALID_ARGS; + } + + mal_atomic_exchange_32(&pSRC->config.sampleRateIn, sampleRateIn); return MAL_SUCCESS; } mal_result mal_src_set_output_sample_rate(mal_src* pSRC, mal_uint32 sampleRateOut) { - if (pSRC == NULL) return MAL_INVALID_ARGS; + 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; + mal_atomic_exchange_32(&pSRC->config.sampleRateOut, sampleRateOut); return MAL_SUCCESS; } -mal_uint32 mal_src_read_frames(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut) +mal_uint64 mal_src_read_deinterleaved(mal_src* pSRC, mal_uint64 frameCount, void** ppSamplesOut, void* pUserData) { - return mal_src_read_frames_ex(pSRC, frameCount, pFramesOut, MAL_FALSE); + return mal_src_read_deinterleaved_ex(pSRC, frameCount, ppSamplesOut, MAL_FALSE, pUserData); } -mal_uint32 mal_src_read_frames_ex(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush) +mal_uint64 mal_src_read_deinterleaved_ex(mal_src* pSRC, mal_uint64 frameCount, void** ppSamplesOut, mal_bool32 flush, void* pUserData) { - if (pSRC == NULL || frameCount == 0 || pFramesOut == NULL) return 0; + if (pSRC == NULL || frameCount == 0 || ppSamplesOut == 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... - switch (algorithm) - { - case mal_src_algorithm_none: return mal_src_read_frames_passthrough(pSRC, frameCount, pFramesOut, flush); - case mal_src_algorithm_linear: return mal_src_read_frames_linear(pSRC, frameCount, pFramesOut, flush); - default: return 0; + // Can use a function pointer for this. + switch (algorithm) { + case mal_src_algorithm_none: return mal_src_read_deinterleaved__passthrough(pSRC, frameCount, ppSamplesOut, flush, pUserData); + case mal_src_algorithm_linear: return mal_src_read_deinterleaved__linear( pSRC, frameCount, ppSamplesOut, flush, pUserData); + default: break; } + + // Should never get here. + return 0; } -mal_uint32 mal_src_read_frames_passthrough(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush) +mal_uint64 mal_src_read_deinterleaved__passthrough(mal_src* pSRC, mal_uint64 frameCount, void** ppSamplesOut, mal_bool32 flush, void* pUserData) { - mal_assert(pSRC != NULL); - mal_assert(frameCount > 0); - mal_assert(pFramesOut != NULL); - (void)flush; // Passthrough need not care about flushing. - // Fast path. No need for data conversion - just pass right through. - if (pSRC->config.formatIn == pSRC->config.formatOut) { - return pSRC->onRead(pSRC, frameCount, pFramesOut, pSRC->pUserData); - } - - // Slower path. Need to do a format conversion. - mal_uint32 totalFramesRead = 0; - while (frameCount > 0) { - mal_uint8 pStagingBuffer[MAL_MAX_CHANNELS * 2048]; - mal_uint32 stagingBufferSizeInFrames = sizeof(pStagingBuffer) / mal_get_sample_size_in_bytes(pSRC->config.formatIn) / pSRC->config.channels; - mal_uint32 framesToRead = stagingBufferSizeInFrames; - if (framesToRead > frameCount) { - framesToRead = frameCount; + if (frameCount <= 0xFFFFFFFF) { + return pSRC->config.onReadDeinterleaved(pSRC, (mal_uint32)frameCount, ppSamplesOut, pUserData); + } else { + float* ppNextSamplesOut[MAL_MAX_CHANNELS]; + for (mal_uint32 iChannel = 0; iChannel < pSRC->config.channels; ++iChannel) { + ppNextSamplesOut[iChannel] = (float*)ppSamplesOut[iChannel]; } - mal_uint32 framesRead = pSRC->onRead(pSRC, framesToRead, pStagingBuffer, pSRC->pUserData); - if (framesRead == 0) { - break; + mal_uint64 totalFramesRead = 0; + while (totalFramesRead < frameCount) { + mal_uint64 framesRemaining = frameCount - totalFramesRead; + mal_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > 0xFFFFFFFF) { + framesToReadRightNow = 0xFFFFFFFF; + } + + mal_uint32 framesRead = (mal_uint32)pSRC->config.onReadDeinterleaved(pSRC, (mal_uint32)framesToReadRightNow, (void**)ppNextSamplesOut, pUserData); + if (framesRead == 0) { + break; + } + + totalFramesRead += framesRead; + for (mal_uint32 iChannel = 0; iChannel < pSRC->config.channels; ++iChannel) { + ppNextSamplesOut[iChannel] += framesRead; + } } - mal_pcm_convert(pFramesOut, pSRC->config.formatOut, pStagingBuffer, pSRC->config.formatIn, framesRead * pSRC->config.channels); - - pFramesOut = (mal_uint8*)pFramesOut + (framesRead * pSRC->config.channels * mal_get_sample_size_in_bytes(pSRC->config.formatOut)); - frameCount -= framesRead; - totalFramesRead += framesRead; + return totalFramesRead; } - - return totalFramesRead; } -mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush) +mal_uint64 mal_src_read_deinterleaved__linear(mal_src* pSRC, mal_uint64 frameCount, void** ppSamplesOut, mal_bool32 flush, void* pUserData) { + (void)flush; // No flushing at the moment. + mal_assert(pSRC != NULL); mal_assert(frameCount > 0); - mal_assert(pFramesOut != NULL); + mal_assert(ppSamplesOut != NULL); - // For linear SRC, the bin is only 2 frames: 1 prior, 1 future. + float* ppNextSamplesOut[MAL_MAX_CHANNELS]; + mal_copy_memory(ppNextSamplesOut, ppSamplesOut, sizeof(void*) * pSRC->config.channels); - // Load the bin if necessary. - if (!pSRC->linear.isPrevFramesLoaded) { - mal_uint32 framesRead = mal_src_cache_read_frames(&pSRC->cache, 1, pSRC->bin); - if (framesRead == 0) { - return 0; - } - pSRC->linear.isPrevFramesLoaded = MAL_TRUE; - } - if (!pSRC->linear.isNextFramesLoaded) { - mal_uint32 framesRead = mal_src_cache_read_frames(&pSRC->cache, 1, pSRC->bin + pSRC->config.channels); - if (framesRead == 0) { - return 0; - } - pSRC->linear.isNextFramesLoaded = MAL_TRUE; - } float factor = (float)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut; - mal_uint32 totalFramesRead = 0; - while (frameCount > 0) { - // The bin is where the previous and next frames are located. - float* pPrevFrame = pSRC->bin; - float* pNextFrame = pSRC->bin + pSRC->config.channels; + mal_uint32 maxFrameCountPerChunkIn = mal_countof(pSRC->samplesFromClient[0]); - float pFrame[MAL_MAX_CHANNELS]; - mal_blend_f32(pFrame, pPrevFrame, pNextFrame, pSRC->linear.alpha, pSRC->config.channels); + mal_uint64 totalFramesRead = 0; + while (totalFramesRead < frameCount) { + mal_uint64 framesRemaining = frameCount - totalFramesRead; + mal_uint64 framesToRead = framesRemaining; + if (framesToRead > 16384) { + framesToRead = 16384; // <-- Keep this small because we're using 32-bit floats for calculating sample positions and I don't want to run out of precision with huge sample counts. + } - pSRC->linear.alpha += factor; - // The new alpha value is how we determine whether or not we need to read fresh frames. - mal_uint32 framesToReadFromClient = (mal_uint32)pSRC->linear.alpha; - pSRC->linear.alpha = pSRC->linear.alpha - framesToReadFromClient; + // Read Input Data + // =============== + float tBeg = pSRC->linear.t; + float tEnd = tBeg + (framesToRead*factor); - for (mal_uint32 i = 0; i < framesToReadFromClient; ++i) { - for (mal_uint32 j = 0; j < pSRC->config.channels; ++j) { - pPrevFrame[j] = pNextFrame[j]; + mal_uint32 framesToReadFromClient = (mal_uint32)(tEnd) + 1 + 1; // +1 to make tEnd 1-based and +1 because we always need to an extra sample for interpolation. + if (framesToReadFromClient >= maxFrameCountPerChunkIn) { + framesToReadFromClient = maxFrameCountPerChunkIn; + } + + float* ppSamplesFromClient[MAL_MAX_CHANNELS]; + for (mal_uint32 iChannel = 0; iChannel < pSRC->config.channels; ++iChannel) { + ppSamplesFromClient[iChannel] = pSRC->samplesFromClient[iChannel] + pSRC->linear.leftoverFrames; + } + + mal_uint32 framesReadFromClient = 0; + if (framesToReadFromClient > pSRC->linear.leftoverFrames) { + framesReadFromClient = (mal_uint32)pSRC->config.onReadDeinterleaved(pSRC, (mal_uint32)framesToReadFromClient - pSRC->linear.leftoverFrames, (void**)ppSamplesFromClient, pUserData); + } + + framesReadFromClient += pSRC->linear.leftoverFrames; // <-- You can sort of think of it as though we've re-read the leftover samples from the client. + if (framesReadFromClient < 2) { + break; + } + + for (mal_uint32 iChannel = 0; iChannel < pSRC->config.channels; ++iChannel) { + ppSamplesFromClient[iChannel] = pSRC->samplesFromClient[iChannel]; + } + + + // Write Output Data + // ================= + + // At this point we have a bunch of frames that the client has given to us for processing. From this we can determine the maximum number of output frames + // that can be processed from this input. We want to output as many samples as possible from our input data. + float tAvailable = framesReadFromClient - tBeg; + + mal_uint32 maxOutputFramesToRead = (mal_uint32)(tAvailable / factor); + if (maxOutputFramesToRead == 0) { + maxOutputFramesToRead = 1; + } + if (maxOutputFramesToRead > framesToRead) { + maxOutputFramesToRead = (mal_uint32)framesToRead; + } + + // Output frames are always read in groups of 4 because I'm planning on using this as a reference for some SIMD-y stuff later. + mal_uint32 maxOutputFramesToRead4 = maxOutputFramesToRead/4; + for (mal_uint32 iChannel = 0; iChannel < pSRC->config.channels; ++iChannel) { + float t0 = pSRC->linear.t + factor*0; + float t1 = pSRC->linear.t + factor*1; + float t2 = pSRC->linear.t + factor*2; + float t3 = pSRC->linear.t + factor*3; + + for (mal_uint32 iFrameOut = 0; iFrameOut < maxOutputFramesToRead4; iFrameOut += 1) { + float iPrevSample0 = (float)floor(t0); + float iPrevSample1 = (float)floor(t1); + float iPrevSample2 = (float)floor(t2); + float iPrevSample3 = (float)floor(t3); + + float iNextSample0 = iPrevSample0 + 1; + float iNextSample1 = iPrevSample1 + 1; + float iNextSample2 = iPrevSample2 + 1; + float iNextSample3 = iPrevSample3 + 1; + + float alpha0 = t0 - iPrevSample0; + float alpha1 = t1 - iPrevSample1; + float alpha2 = t2 - iPrevSample2; + float alpha3 = t3 - iPrevSample3; + + float prevSample0 = ppSamplesFromClient[iChannel][(mal_uint32)iPrevSample0]; + float prevSample1 = ppSamplesFromClient[iChannel][(mal_uint32)iPrevSample1]; + float prevSample2 = ppSamplesFromClient[iChannel][(mal_uint32)iPrevSample2]; + float prevSample3 = ppSamplesFromClient[iChannel][(mal_uint32)iPrevSample3]; + + float nextSample0 = ppSamplesFromClient[iChannel][(mal_uint32)iNextSample0]; + float nextSample1 = ppSamplesFromClient[iChannel][(mal_uint32)iNextSample1]; + float nextSample2 = ppSamplesFromClient[iChannel][(mal_uint32)iNextSample2]; + float nextSample3 = ppSamplesFromClient[iChannel][(mal_uint32)iNextSample3]; + + ppNextSamplesOut[iChannel][iFrameOut*4 + 0] = mal_mix_f32(prevSample0, nextSample0, alpha0); + ppNextSamplesOut[iChannel][iFrameOut*4 + 1] = mal_mix_f32(prevSample1, nextSample1, alpha1); + ppNextSamplesOut[iChannel][iFrameOut*4 + 2] = mal_mix_f32(prevSample2, nextSample2, alpha2); + ppNextSamplesOut[iChannel][iFrameOut*4 + 3] = mal_mix_f32(prevSample3, nextSample3, alpha3); + + t0 += factor*4; + t1 += factor*4; + t2 += factor*4; + t3 += factor*4; } - mal_uint32 framesRead = mal_src_cache_read_frames(&pSRC->cache, 1, pNextFrame); - if (framesRead == 0) { - for (mal_uint32 j = 0; j < pSRC->config.channels; ++j) { - pNextFrame[j] = 0; - } + float t = pSRC->linear.t + (factor*maxOutputFramesToRead4*4); + for (mal_uint32 iFrameOut = (maxOutputFramesToRead4*4); iFrameOut < maxOutputFramesToRead; iFrameOut += 1) { + float iPrevSample = (float)floor(t); + float iNextSample = iPrevSample + 1; + float alpha = t - iPrevSample; - if (pSRC->linear.isNextFramesLoaded) { - pSRC->linear.isNextFramesLoaded = MAL_FALSE; - } else { - if (flush) { - pSRC->linear.isPrevFramesLoaded = MAL_FALSE; - } - } + float prevSample = ppSamplesFromClient[iChannel][(mal_uint32)iPrevSample]; + float nextSample = ppSamplesFromClient[iChannel][(mal_uint32)iNextSample]; - break; + ppNextSamplesOut[iChannel][iFrameOut] = mal_mix_f32(prevSample, nextSample, alpha); + + t += factor; + } + + ppNextSamplesOut[iChannel] += maxOutputFramesToRead; + } + + totalFramesRead += maxOutputFramesToRead; + + + // Residual + // ======== + float tNext = pSRC->linear.t + (maxOutputFramesToRead*factor); + + pSRC->linear.t = tNext; + mal_assert(tNext <= framesReadFromClient+1); + + mal_uint32 iNextFrame = (mal_uint32)floor(tNext); + pSRC->linear.leftoverFrames = framesReadFromClient - iNextFrame; + pSRC->linear.t = tNext - iNextFrame; + + for (mal_uint32 iChannel = 0; iChannel < pSRC->config.channels; ++iChannel) { + for (mal_uint32 iFrame = 0; iFrame < pSRC->linear.leftoverFrames; ++iFrame) { + float sample = ppSamplesFromClient[iChannel][framesReadFromClient-pSRC->linear.leftoverFrames + iFrame]; + ppSamplesFromClient[iChannel][iFrame] = sample; } } - mal_pcm_convert(pFramesOut, pSRC->config.formatOut, pFrame, mal_format_f32, 1 * pSRC->config.channels); - - pFramesOut = (mal_uint8*)pFramesOut + (1 * pSRC->config.channels * mal_get_sample_size_in_bytes(pSRC->config.formatOut)); - frameCount -= 1; - totalFramesRead += 1; - - // If there's no frames available we need to get out of this loop. - if (!pSRC->linear.isNextFramesLoaded && (!flush || !pSRC->linear.isPrevFramesLoaded)) { + + // Exit the loop if we've found everything from the client. + if (framesReadFromClient < framesToReadFromClient) { break; } } @@ -10467,31 +18490,10 @@ mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -void mal_pcm_u8_to_s16(short* pOut, const unsigned char* pIn, unsigned int count); -void mal_pcm_u8_to_s24(void* pOut, const unsigned char* pIn, unsigned int count); -void mal_pcm_u8_to_s32(int* pOut, const unsigned char* pIn, unsigned int count); -void mal_pcm_u8_to_f32(float* pOut, const unsigned char* pIn, unsigned int count); -void mal_pcm_s16_to_u8(unsigned char* pOut, const short* pIn, unsigned int count); -void mal_pcm_s16_to_s24(void* pOut, const short* pIn, unsigned int count); -void mal_pcm_s16_to_s32(int* pOut, const short* pIn, unsigned int count); -void mal_pcm_s16_to_f32(float* pOut, const short* pIn, unsigned int count); -void mal_pcm_s24_to_u8(unsigned char* pOut, const void* pIn, unsigned int count); -void mal_pcm_s24_to_s16(short* pOut, const void* pIn, unsigned int count); -void mal_pcm_s24_to_s32(int* pOut, const void* pIn, unsigned int count); -void mal_pcm_s24_to_f32(float* pOut, const void* pIn, unsigned int count); -void mal_pcm_s32_to_u8(unsigned char* pOut, const int* pIn, unsigned int count); -void mal_pcm_s32_to_s16(short* pOut, const int* pIn, unsigned int count); -void mal_pcm_s32_to_s24(void* pOut, const int* pIn, unsigned int count); -void mal_pcm_s32_to_f32(float* pOut, const int* pIn, unsigned int count); -void mal_pcm_f32_to_u8(unsigned char* pOut, const float* pIn, unsigned int count); -void mal_pcm_f32_to_s16(short* pOut, const float* pIn, unsigned int count); -void mal_pcm_f32_to_s24(void* pOut, const float* pIn, unsigned int count); -void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count); - -void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_format formatIn, unsigned int sampleCount) +void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_format formatIn, mal_uint64 sampleCount, mal_dither_mode ditherMode) { if (formatOut == formatIn) { - mal_copy_memory(pOut, pIn, sampleCount * mal_get_sample_size_in_bytes(formatOut)); + mal_copy_memory_64(pOut, pIn, sampleCount * mal_get_bytes_per_sample(formatOut)); return; } @@ -10501,10 +18503,10 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form { switch (formatOut) { - case mal_format_s16: mal_pcm_u8_to_s16((short*)pOut, (const unsigned char*)pIn, sampleCount); return; - case mal_format_s24: mal_pcm_u8_to_s24( pOut, (const unsigned char*)pIn, sampleCount); return; - case mal_format_s32: mal_pcm_u8_to_s32( (int*)pOut, (const unsigned char*)pIn, sampleCount); return; - case mal_format_f32: mal_pcm_u8_to_f32((float*)pOut, (const unsigned char*)pIn, sampleCount); return; + case mal_format_s16: mal_pcm_u8_to_s16(pOut, pIn, sampleCount, ditherMode); return; + case mal_format_s24: mal_pcm_u8_to_s24(pOut, pIn, sampleCount, ditherMode); return; + case mal_format_s32: mal_pcm_u8_to_s32(pOut, pIn, sampleCount, ditherMode); return; + case mal_format_f32: mal_pcm_u8_to_f32(pOut, pIn, sampleCount, ditherMode); return; default: break; } } break; @@ -10513,10 +18515,10 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form { switch (formatOut) { - case mal_format_u8: mal_pcm_s16_to_u8( (unsigned char*)pOut, (const short*)pIn, sampleCount); return; - case mal_format_s24: mal_pcm_s16_to_s24( pOut, (const short*)pIn, sampleCount); return; - case mal_format_s32: mal_pcm_s16_to_s32( (int*)pOut, (const short*)pIn, sampleCount); return; - case mal_format_f32: mal_pcm_s16_to_f32( (float*)pOut, (const short*)pIn, sampleCount); return; + case mal_format_u8: mal_pcm_s16_to_u8( pOut, pIn, sampleCount, ditherMode); return; + case mal_format_s24: mal_pcm_s16_to_s24(pOut, pIn, sampleCount, ditherMode); return; + case mal_format_s32: mal_pcm_s16_to_s32(pOut, pIn, sampleCount, ditherMode); return; + case mal_format_f32: mal_pcm_s16_to_f32(pOut, pIn, sampleCount, ditherMode); return; default: break; } } break; @@ -10525,10 +18527,10 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form { switch (formatOut) { - case mal_format_u8: mal_pcm_s24_to_u8( (unsigned char*)pOut, pIn, sampleCount); return; - case mal_format_s16: mal_pcm_s24_to_s16( (short*)pOut, pIn, sampleCount); return; - case mal_format_s32: mal_pcm_s24_to_s32( (int*)pOut, pIn, sampleCount); return; - case mal_format_f32: mal_pcm_s24_to_f32( (float*)pOut, pIn, sampleCount); return; + case mal_format_u8: mal_pcm_s24_to_u8( pOut, pIn, sampleCount, ditherMode); return; + case mal_format_s16: mal_pcm_s24_to_s16(pOut, pIn, sampleCount, ditherMode); return; + case mal_format_s32: mal_pcm_s24_to_s32(pOut, pIn, sampleCount, ditherMode); return; + case mal_format_f32: mal_pcm_s24_to_f32(pOut, pIn, sampleCount, ditherMode); return; default: break; } } break; @@ -10537,10 +18539,10 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form { switch (formatOut) { - case mal_format_u8: mal_pcm_s32_to_u8( (unsigned char*)pOut, (const int*)pIn, sampleCount); return; - case mal_format_s16: mal_pcm_s32_to_s16( (short*)pOut, (const int*)pIn, sampleCount); return; - case mal_format_s24: mal_pcm_s32_to_s24( pOut, (const int*)pIn, sampleCount); return; - case mal_format_f32: mal_pcm_s32_to_f32( (float*)pOut, (const int*)pIn, sampleCount); return; + case mal_format_u8: mal_pcm_s32_to_u8( pOut, pIn, sampleCount, ditherMode); return; + case mal_format_s16: mal_pcm_s32_to_s16(pOut, pIn, sampleCount, ditherMode); return; + case mal_format_s24: mal_pcm_s32_to_s24(pOut, pIn, sampleCount, ditherMode); return; + case mal_format_f32: mal_pcm_s32_to_f32(pOut, pIn, sampleCount, ditherMode); return; default: break; } } break; @@ -10549,10 +18551,10 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form { switch (formatOut) { - case mal_format_u8: mal_pcm_f32_to_u8( (unsigned char*)pOut, (const float*)pIn, sampleCount); return; - case mal_format_s16: mal_pcm_f32_to_s16( (short*)pOut, (const float*)pIn, sampleCount); return; - case mal_format_s24: mal_pcm_f32_to_s24( pOut, (const float*)pIn, sampleCount); return; - case mal_format_s32: mal_pcm_f32_to_s32( (int*)pOut, (const float*)pIn, sampleCount); return; + case mal_format_u8: mal_pcm_f32_to_u8( pOut, pIn, sampleCount, ditherMode); return; + case mal_format_s16: mal_pcm_f32_to_s16(pOut, pIn, sampleCount, ditherMode); return; + case mal_format_s24: mal_pcm_f32_to_s24(pOut, pIn, sampleCount, ditherMode); return; + case mal_format_s32: mal_pcm_f32_to_s32(pOut, pIn, sampleCount, ditherMode); return; default: break; } } break; @@ -10562,585 +18564,383 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form } -static void mal_rearrange_channels_u8(mal_uint8* pFrame, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS]) -{ - mal_uint8 temp[MAL_MAX_CHANNELS]; - mal_copy_memory(temp, pFrame, sizeof(temp[0]) * channels); - switch (channels) { - case 18: pFrame[17] = temp[channelMap[17]]; - case 17: pFrame[16] = temp[channelMap[16]]; - case 16: pFrame[15] = temp[channelMap[15]]; - case 15: pFrame[14] = temp[channelMap[14]]; - case 14: pFrame[13] = temp[channelMap[13]]; - case 13: pFrame[12] = temp[channelMap[12]]; - case 12: pFrame[11] = temp[channelMap[11]]; - case 11: pFrame[10] = temp[channelMap[10]]; - case 10: pFrame[ 9] = temp[channelMap[ 9]]; - case 9: pFrame[ 8] = temp[channelMap[ 8]]; - case 8: pFrame[ 7] = temp[channelMap[ 7]]; - case 7: pFrame[ 6] = temp[channelMap[ 6]]; - case 6: pFrame[ 5] = temp[channelMap[ 5]]; - case 5: pFrame[ 4] = temp[channelMap[ 4]]; - case 4: pFrame[ 3] = temp[channelMap[ 3]]; - case 3: pFrame[ 2] = temp[channelMap[ 2]]; - case 2: pFrame[ 1] = temp[channelMap[ 1]]; - case 1: pFrame[ 0] = temp[channelMap[ 0]]; - } +typedef struct +{ + mal_dsp* pDSP; + mal_bool32 flush; + void* pUserDataForClient; +} mal_dsp_callback_data; + +mal_uint32 mal_dsp__pre_format_converter_on_read(mal_format_converter* pConverter, mal_uint32 frameCount, void* pFramesOut, void* pUserData) +{ + (void)pConverter; + + mal_dsp_callback_data* pData = (mal_dsp_callback_data*)pUserData; + mal_assert(pData != NULL); + + mal_dsp* pDSP = pData->pDSP; + mal_assert(pDSP != NULL); + + return pDSP->onRead(pDSP, frameCount, pFramesOut, pData->pUserDataForClient); } -static void mal_rearrange_channels_s16(mal_int16* pFrame, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS]) +mal_uint32 mal_dsp__post_format_converter_on_read(mal_format_converter* pConverter, mal_uint32 frameCount, void* pFramesOut, void* pUserData) { - mal_int16 temp[MAL_MAX_CHANNELS]; - mal_copy_memory(temp, pFrame, sizeof(temp[0]) * channels); + (void)pConverter; - switch (channels) { - case 18: pFrame[17] = temp[channelMap[17]]; - case 17: pFrame[16] = temp[channelMap[16]]; - case 16: pFrame[15] = temp[channelMap[15]]; - case 15: pFrame[14] = temp[channelMap[14]]; - case 14: pFrame[13] = temp[channelMap[13]]; - case 13: pFrame[12] = temp[channelMap[12]]; - case 12: pFrame[11] = temp[channelMap[11]]; - case 11: pFrame[10] = temp[channelMap[10]]; - case 10: pFrame[ 9] = temp[channelMap[ 9]]; - case 9: pFrame[ 8] = temp[channelMap[ 8]]; - case 8: pFrame[ 7] = temp[channelMap[ 7]]; - case 7: pFrame[ 6] = temp[channelMap[ 6]]; - case 6: pFrame[ 5] = temp[channelMap[ 5]]; - case 5: pFrame[ 4] = temp[channelMap[ 4]]; - case 4: pFrame[ 3] = temp[channelMap[ 3]]; - case 3: pFrame[ 2] = temp[channelMap[ 2]]; - case 2: pFrame[ 1] = temp[channelMap[ 1]]; - case 1: pFrame[ 0] = temp[channelMap[ 0]]; - } + mal_dsp_callback_data* pData = (mal_dsp_callback_data*)pUserData; + mal_assert(pData != NULL); + + mal_dsp* pDSP = pData->pDSP; + mal_assert(pDSP != NULL); + + // When this version of this callback is used it means we're reading directly from the client. + mal_assert(pDSP->isPreFormatConversionRequired == MAL_FALSE); + mal_assert(pDSP->isChannelRoutingRequired == MAL_FALSE); + mal_assert(pDSP->isSRCRequired == MAL_FALSE); + + return pDSP->onRead(pDSP, frameCount, pFramesOut, pData->pUserDataForClient); } -static void mal_rearrange_channels_s32(mal_int32* pFrame, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS]) +mal_uint32 mal_dsp__post_format_converter_on_read_deinterleaved(mal_format_converter* pConverter, mal_uint32 frameCount, void** ppSamplesOut, void* pUserData) { - mal_int32 temp[MAL_MAX_CHANNELS]; - mal_copy_memory(temp, pFrame, sizeof(temp[0]) * channels); + (void)pConverter; - switch (channels) { - case 18: pFrame[17] = temp[channelMap[17]]; - case 17: pFrame[16] = temp[channelMap[16]]; - case 16: pFrame[15] = temp[channelMap[15]]; - case 15: pFrame[14] = temp[channelMap[14]]; - case 14: pFrame[13] = temp[channelMap[13]]; - case 13: pFrame[12] = temp[channelMap[12]]; - case 12: pFrame[11] = temp[channelMap[11]]; - case 11: pFrame[10] = temp[channelMap[10]]; - case 10: pFrame[ 9] = temp[channelMap[ 9]]; - case 9: pFrame[ 8] = temp[channelMap[ 8]]; - case 8: pFrame[ 7] = temp[channelMap[ 7]]; - case 7: pFrame[ 6] = temp[channelMap[ 6]]; - case 6: pFrame[ 5] = temp[channelMap[ 5]]; - case 5: pFrame[ 4] = temp[channelMap[ 4]]; - case 4: pFrame[ 3] = temp[channelMap[ 3]]; - case 3: pFrame[ 2] = temp[channelMap[ 2]]; - case 2: pFrame[ 1] = temp[channelMap[ 1]]; - case 1: pFrame[ 0] = temp[channelMap[ 0]]; - } -} + mal_dsp_callback_data* pData = (mal_dsp_callback_data*)pUserData; + mal_assert(pData != NULL); -static void mal_rearrange_channels_f32(float* pFrame, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS]) -{ - float temp[MAL_MAX_CHANNELS]; - mal_copy_memory(temp, pFrame, sizeof(temp[0]) * channels); + mal_dsp* pDSP = pData->pDSP; + mal_assert(pDSP != NULL); - switch (channels) { - case 18: pFrame[17] = temp[channelMap[17]]; - case 17: pFrame[16] = temp[channelMap[16]]; - case 16: pFrame[15] = temp[channelMap[15]]; - case 15: pFrame[14] = temp[channelMap[14]]; - case 14: pFrame[13] = temp[channelMap[13]]; - case 13: pFrame[12] = temp[channelMap[12]]; - case 12: pFrame[11] = temp[channelMap[11]]; - case 11: pFrame[10] = temp[channelMap[10]]; - case 10: pFrame[ 9] = temp[channelMap[ 9]]; - case 9: pFrame[ 8] = temp[channelMap[ 8]]; - case 8: pFrame[ 7] = temp[channelMap[ 7]]; - case 7: pFrame[ 6] = temp[channelMap[ 6]]; - case 6: pFrame[ 5] = temp[channelMap[ 5]]; - case 5: pFrame[ 4] = temp[channelMap[ 4]]; - case 4: pFrame[ 3] = temp[channelMap[ 3]]; - case 3: pFrame[ 2] = temp[channelMap[ 2]]; - case 2: pFrame[ 1] = temp[channelMap[ 1]]; - case 1: pFrame[ 0] = temp[channelMap[ 0]]; - } -} - -static void mal_rearrange_channels_generic(void* pFrame, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS], mal_format format) -{ - mal_uint32 sampleSizeInBytes = mal_get_sample_size_in_bytes(format); - - mal_uint8 temp[MAL_MAX_CHANNELS * 8]; // x8 to ensure it's large enough for all formats. - mal_copy_memory(temp, pFrame, sampleSizeInBytes * channels); - - switch (channels) { - case 18: mal_copy_memory((mal_uint8*)pFrame + (17 * sampleSizeInBytes), &temp[channelMap[17] * sampleSizeInBytes], sampleSizeInBytes); - case 17: mal_copy_memory((mal_uint8*)pFrame + (16 * sampleSizeInBytes), &temp[channelMap[16] * sampleSizeInBytes], sampleSizeInBytes); - case 16: mal_copy_memory((mal_uint8*)pFrame + (15 * sampleSizeInBytes), &temp[channelMap[15] * sampleSizeInBytes], sampleSizeInBytes); - case 15: mal_copy_memory((mal_uint8*)pFrame + (14 * sampleSizeInBytes), &temp[channelMap[14] * sampleSizeInBytes], sampleSizeInBytes); - case 14: mal_copy_memory((mal_uint8*)pFrame + (13 * sampleSizeInBytes), &temp[channelMap[13] * sampleSizeInBytes], sampleSizeInBytes); - case 13: mal_copy_memory((mal_uint8*)pFrame + (12 * sampleSizeInBytes), &temp[channelMap[12] * sampleSizeInBytes], sampleSizeInBytes); - case 12: mal_copy_memory((mal_uint8*)pFrame + (11 * sampleSizeInBytes), &temp[channelMap[11] * sampleSizeInBytes], sampleSizeInBytes); - case 11: mal_copy_memory((mal_uint8*)pFrame + (10 * sampleSizeInBytes), &temp[channelMap[10] * sampleSizeInBytes], sampleSizeInBytes); - case 10: mal_copy_memory((mal_uint8*)pFrame + ( 9 * sampleSizeInBytes), &temp[channelMap[ 9] * sampleSizeInBytes], sampleSizeInBytes); - case 9: mal_copy_memory((mal_uint8*)pFrame + ( 8 * sampleSizeInBytes), &temp[channelMap[ 8] * sampleSizeInBytes], sampleSizeInBytes); - case 8: mal_copy_memory((mal_uint8*)pFrame + ( 7 * sampleSizeInBytes), &temp[channelMap[ 7] * sampleSizeInBytes], sampleSizeInBytes); - case 7: mal_copy_memory((mal_uint8*)pFrame + ( 6 * sampleSizeInBytes), &temp[channelMap[ 6] * sampleSizeInBytes], sampleSizeInBytes); - case 6: mal_copy_memory((mal_uint8*)pFrame + ( 5 * sampleSizeInBytes), &temp[channelMap[ 5] * sampleSizeInBytes], sampleSizeInBytes); - case 5: mal_copy_memory((mal_uint8*)pFrame + ( 4 * sampleSizeInBytes), &temp[channelMap[ 4] * sampleSizeInBytes], sampleSizeInBytes); - case 4: mal_copy_memory((mal_uint8*)pFrame + ( 3 * sampleSizeInBytes), &temp[channelMap[ 3] * sampleSizeInBytes], sampleSizeInBytes); - case 3: mal_copy_memory((mal_uint8*)pFrame + ( 2 * sampleSizeInBytes), &temp[channelMap[ 2] * sampleSizeInBytes], sampleSizeInBytes); - case 2: mal_copy_memory((mal_uint8*)pFrame + ( 1 * sampleSizeInBytes), &temp[channelMap[ 1] * sampleSizeInBytes], sampleSizeInBytes); - case 1: mal_copy_memory((mal_uint8*)pFrame + ( 0 * sampleSizeInBytes), &temp[channelMap[ 0] * sampleSizeInBytes], sampleSizeInBytes); - } -} - -static void mal_rearrange_channels(void* pFrame, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS], mal_format format) -{ - switch (format) - { - case mal_format_u8: mal_rearrange_channels_u8( (mal_uint8*)pFrame, channels, channelMap); break; - case mal_format_s16: mal_rearrange_channels_s16((mal_int16*)pFrame, channels, channelMap); break; - case mal_format_s32: mal_rearrange_channels_s32((mal_int32*)pFrame, channels, channelMap); break; - case mal_format_f32: mal_rearrange_channels_f32( (float*)pFrame, channels, channelMap); break; - default: mal_rearrange_channels_generic(pFrame, channels, channelMap, format); break; - } -} - -static void mal_dsp_mix_channels__dec(float* pFramesOut, mal_uint32 channelsOut, const mal_uint8 channelMapOut[MAL_MAX_CHANNELS], const float* pFramesIn, mal_uint32 channelsIn, const mal_uint8 channelMapIn[MAL_MAX_CHANNELS], mal_uint32 frameCount, mal_channel_mix_mode mode) -{ - mal_assert(pFramesOut != NULL); - mal_assert(channelsOut > 0); - mal_assert(pFramesIn != NULL); - mal_assert(channelsIn > 0); - mal_assert(channelsOut < channelsIn); - - (void)channelMapOut; - (void)channelMapIn; - - if (mode == mal_channel_mix_mode_basic) { - // Basic mode is where we just drop excess channels. - for (mal_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) { - switch (channelsOut) { - case 17: pFramesOut[iFrame*channelsOut+16] = pFramesIn[iFrame*channelsIn+16]; - case 16: pFramesOut[iFrame*channelsOut+15] = pFramesIn[iFrame*channelsIn+15]; - case 15: pFramesOut[iFrame*channelsOut+14] = pFramesIn[iFrame*channelsIn+14]; - case 14: pFramesOut[iFrame*channelsOut+13] = pFramesIn[iFrame*channelsIn+13]; - case 13: pFramesOut[iFrame*channelsOut+12] = pFramesIn[iFrame*channelsIn+12]; - case 12: pFramesOut[iFrame*channelsOut+11] = pFramesIn[iFrame*channelsIn+11]; - case 11: pFramesOut[iFrame*channelsOut+10] = pFramesIn[iFrame*channelsIn+10]; - case 10: pFramesOut[iFrame*channelsOut+ 9] = pFramesIn[iFrame*channelsIn+ 9]; - case 9: pFramesOut[iFrame*channelsOut+ 8] = pFramesIn[iFrame*channelsIn+ 8]; - case 8: pFramesOut[iFrame*channelsOut+ 7] = pFramesIn[iFrame*channelsIn+ 7]; - case 7: pFramesOut[iFrame*channelsOut+ 6] = pFramesIn[iFrame*channelsIn+ 6]; - case 6: pFramesOut[iFrame*channelsOut+ 5] = pFramesIn[iFrame*channelsIn+ 5]; - case 5: pFramesOut[iFrame*channelsOut+ 4] = pFramesIn[iFrame*channelsIn+ 4]; - case 4: pFramesOut[iFrame*channelsOut+ 3] = pFramesIn[iFrame*channelsIn+ 3]; - case 3: pFramesOut[iFrame*channelsOut+ 2] = pFramesIn[iFrame*channelsIn+ 2]; - case 2: pFramesOut[iFrame*channelsOut+ 1] = pFramesIn[iFrame*channelsIn+ 1]; - case 1: pFramesOut[iFrame*channelsOut+ 0] = pFramesIn[iFrame*channelsIn+ 0]; - } - } + if (!pDSP->isChannelRoutingAtStart) { + return (mal_uint32)mal_channel_router_read_deinterleaved(&pDSP->channelRouter, frameCount, ppSamplesOut, pUserData); } else { - // Blend mode is where we just use simple averaging to blend based on spacial locality. - if (channelsOut == 1) { - for (mal_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) { - float total = 0; - switch (channelsIn) { - case 18: total += pFramesIn[iFrame*channelsIn+17]; - case 17: total += pFramesIn[iFrame*channelsIn+16]; - case 16: total += pFramesIn[iFrame*channelsIn+15]; - case 15: total += pFramesIn[iFrame*channelsIn+14]; - case 14: total += pFramesIn[iFrame*channelsIn+13]; - case 13: total += pFramesIn[iFrame*channelsIn+12]; - case 12: total += pFramesIn[iFrame*channelsIn+11]; - case 11: total += pFramesIn[iFrame*channelsIn+10]; - case 10: total += pFramesIn[iFrame*channelsIn+ 9]; - case 9: total += pFramesIn[iFrame*channelsIn+ 8]; - case 8: total += pFramesIn[iFrame*channelsIn+ 7]; - case 7: total += pFramesIn[iFrame*channelsIn+ 6]; - case 6: total += pFramesIn[iFrame*channelsIn+ 5]; - case 5: total += pFramesIn[iFrame*channelsIn+ 4]; - case 4: total += pFramesIn[iFrame*channelsIn+ 3]; - case 3: total += pFramesIn[iFrame*channelsIn+ 2]; - case 2: total += pFramesIn[iFrame*channelsIn+ 1]; - case 1: total += pFramesIn[iFrame*channelsIn+ 0]; - } - - pFramesOut[iFrame+0] = total / channelsIn; - } - } else if (channelsOut == 2) { - // TODO: Implement proper stereo blending. - mal_dsp_mix_channels__dec(pFramesOut, channelsOut, channelMapOut, pFramesIn, channelsIn, channelMapIn, frameCount, mal_channel_mix_mode_basic); + if (pDSP->isSRCRequired) { + return (mal_uint32)mal_src_read_deinterleaved_ex(&pDSP->src, frameCount, ppSamplesOut, pData->flush, pUserData); } else { - // Fall back to basic mode. - mal_dsp_mix_channels__dec(pFramesOut, channelsOut, channelMapOut, pFramesIn, channelsIn, channelMapIn, frameCount, mal_channel_mix_mode_basic); + return (mal_uint32)mal_format_converter_read_deinterleaved(&pDSP->formatConverterIn, frameCount, ppSamplesOut, pUserData); } } } -static void mal_dsp_mix_channels__inc(float* pFramesOut, mal_uint32 channelsOut, const mal_uint8 channelMapOut[MAL_MAX_CHANNELS], const float* pFramesIn, mal_uint32 channelsIn, const mal_uint8 channelMapIn[MAL_MAX_CHANNELS], mal_uint32 frameCount, mal_channel_mix_mode mode) -{ - mal_assert(pFramesOut != NULL); - mal_assert(channelsOut > 0); - mal_assert(pFramesIn != NULL); - mal_assert(channelsIn > 0); - mal_assert(channelsOut > channelsIn); - - (void)channelMapOut; - (void)channelMapIn; - - if (mode == mal_channel_mix_mode_basic) { - // Basic mode is where we just zero out extra channels. - for (mal_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) { - switch (channelsIn) { - case 17: pFramesOut[iFrame*channelsOut+16] = pFramesIn[iFrame*channelsIn+16]; - case 16: pFramesOut[iFrame*channelsOut+15] = pFramesIn[iFrame*channelsIn+15]; - case 15: pFramesOut[iFrame*channelsOut+14] = pFramesIn[iFrame*channelsIn+14]; - case 14: pFramesOut[iFrame*channelsOut+13] = pFramesIn[iFrame*channelsIn+13]; - case 13: pFramesOut[iFrame*channelsOut+12] = pFramesIn[iFrame*channelsIn+12]; - case 12: pFramesOut[iFrame*channelsOut+11] = pFramesIn[iFrame*channelsIn+11]; - case 11: pFramesOut[iFrame*channelsOut+10] = pFramesIn[iFrame*channelsIn+10]; - case 10: pFramesOut[iFrame*channelsOut+ 9] = pFramesIn[iFrame*channelsIn+ 9]; - case 9: pFramesOut[iFrame*channelsOut+ 8] = pFramesIn[iFrame*channelsIn+ 8]; - case 8: pFramesOut[iFrame*channelsOut+ 7] = pFramesIn[iFrame*channelsIn+ 7]; - case 7: pFramesOut[iFrame*channelsOut+ 6] = pFramesIn[iFrame*channelsIn+ 6]; - case 6: pFramesOut[iFrame*channelsOut+ 5] = pFramesIn[iFrame*channelsIn+ 5]; - case 5: pFramesOut[iFrame*channelsOut+ 4] = pFramesIn[iFrame*channelsIn+ 4]; - case 4: pFramesOut[iFrame*channelsOut+ 3] = pFramesIn[iFrame*channelsIn+ 3]; - case 3: pFramesOut[iFrame*channelsOut+ 2] = pFramesIn[iFrame*channelsIn+ 2]; - case 2: pFramesOut[iFrame*channelsOut+ 1] = pFramesIn[iFrame*channelsIn+ 1]; - case 1: pFramesOut[iFrame*channelsOut+ 0] = pFramesIn[iFrame*channelsIn+ 0]; - } - - // Zero out extra channels. - switch (channelsOut - channelsIn) { - case 17: pFramesOut[iFrame*channelsOut+16 + channelsIn] = 0; - case 16: pFramesOut[iFrame*channelsOut+15 + channelsIn] = 0; - case 15: pFramesOut[iFrame*channelsOut+14 + channelsIn] = 0; - case 14: pFramesOut[iFrame*channelsOut+13 + channelsIn] = 0; - case 13: pFramesOut[iFrame*channelsOut+12 + channelsIn] = 0; - case 12: pFramesOut[iFrame*channelsOut+11 + channelsIn] = 0; - case 11: pFramesOut[iFrame*channelsOut+10 + channelsIn] = 0; - case 10: pFramesOut[iFrame*channelsOut+ 9 + channelsIn] = 0; - case 9: pFramesOut[iFrame*channelsOut+ 8 + channelsIn] = 0; - case 8: pFramesOut[iFrame*channelsOut+ 7 + channelsIn] = 0; - case 7: pFramesOut[iFrame*channelsOut+ 6 + channelsIn] = 0; - case 6: pFramesOut[iFrame*channelsOut+ 5 + channelsIn] = 0; - case 5: pFramesOut[iFrame*channelsOut+ 4 + channelsIn] = 0; - case 4: pFramesOut[iFrame*channelsOut+ 3 + channelsIn] = 0; - case 3: pFramesOut[iFrame*channelsOut+ 2 + channelsIn] = 0; - case 2: pFramesOut[iFrame*channelsOut+ 1 + channelsIn] = 0; - case 1: pFramesOut[iFrame*channelsOut+ 0 + channelsIn] = 0; - } - } - } else { - // Using blended mixing mode. Basically this is just the mode where audio is distributed across all channels - // based on spacial locality. - if (channelsIn == 1) { - for (mal_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) { - switch (channelsOut) { - case 18: pFramesOut[iFrame*channelsOut+17] = pFramesIn[iFrame*channelsIn+0]; - case 17: pFramesOut[iFrame*channelsOut+16] = pFramesIn[iFrame*channelsIn+0]; - case 16: pFramesOut[iFrame*channelsOut+15] = pFramesIn[iFrame*channelsIn+0]; - case 15: pFramesOut[iFrame*channelsOut+14] = pFramesIn[iFrame*channelsIn+0]; - case 14: pFramesOut[iFrame*channelsOut+13] = pFramesIn[iFrame*channelsIn+0]; - case 13: pFramesOut[iFrame*channelsOut+12] = pFramesIn[iFrame*channelsIn+0]; - case 12: pFramesOut[iFrame*channelsOut+11] = pFramesIn[iFrame*channelsIn+0]; - case 11: pFramesOut[iFrame*channelsOut+10] = pFramesIn[iFrame*channelsIn+0]; - case 10: pFramesOut[iFrame*channelsOut+ 9] = pFramesIn[iFrame*channelsIn+0]; - case 9: pFramesOut[iFrame*channelsOut+ 8] = pFramesIn[iFrame*channelsIn+0]; - case 8: pFramesOut[iFrame*channelsOut+ 7] = pFramesIn[iFrame*channelsIn+0]; - case 7: pFramesOut[iFrame*channelsOut+ 6] = pFramesIn[iFrame*channelsIn+0]; - case 6: pFramesOut[iFrame*channelsOut+ 5] = pFramesIn[iFrame*channelsIn+0]; - case 5: pFramesOut[iFrame*channelsOut+ 4] = pFramesIn[iFrame*channelsIn+0]; - case 4: pFramesOut[iFrame*channelsOut+ 3] = pFramesIn[iFrame*channelsIn+0]; - case 3: pFramesOut[iFrame*channelsOut+ 2] = pFramesIn[iFrame*channelsIn+0]; - case 2: pFramesOut[iFrame*channelsOut+ 1] = pFramesIn[iFrame*channelsIn+0]; - case 1: pFramesOut[iFrame*channelsOut+ 0] = pFramesIn[iFrame*channelsIn+0]; - } - } - } else if (channelsIn == 2) { - // TODO: Implement an optimized stereo conversion. - mal_dsp_mix_channels__inc(pFramesOut, channelsOut, channelMapOut, pFramesIn, channelsIn, channelMapIn, frameCount, mal_channel_mix_mode_basic); - } else { - // Fall back to basic mixing mode. - mal_dsp_mix_channels__inc(pFramesOut, channelsOut, channelMapOut, pFramesIn, channelsIn, channelMapIn, frameCount, mal_channel_mix_mode_basic); - } - } -} - -static void mal_dsp_mix_channels(float* pFramesOut, mal_uint32 channelsOut, const mal_uint8 channelMapOut[MAL_MAX_CHANNELS], const float* pFramesIn, mal_uint32 channelsIn, const mal_uint8 channelMapIn[MAL_MAX_CHANNELS], mal_uint32 frameCount, mal_channel_mix_mode mode) -{ - if (channelsIn < channelsOut) { - // Increasing the channel count. - mal_dsp_mix_channels__inc(pFramesOut, channelsOut, channelMapOut, pFramesIn, channelsIn, channelMapIn, frameCount, mode); - } else { - // Decreasing the channel count. - mal_dsp_mix_channels__dec(pFramesOut, channelsOut, channelMapOut, pFramesIn, channelsIn, channelMapIn, frameCount, mode); - } -} - - -mal_uint32 mal_dsp__src_on_read(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, void* pUserData) +mal_uint32 mal_dsp__src_on_read_deinterleaved(mal_src* pSRC, mal_uint32 frameCount, void** ppSamplesOut, void* pUserData) { (void)pSRC; - mal_dsp* pDSP = (mal_dsp*)pUserData; + mal_dsp_callback_data* pData = (mal_dsp_callback_data*)pUserData; + mal_assert(pData != NULL); + + mal_dsp* pDSP = pData->pDSP; mal_assert(pDSP != NULL); - return pDSP->onRead(pDSP, frameCount, pFramesOut, pDSP->pUserDataForOnRead); + // If the channel routing stage is at the front we need to read from that. Otherwise we read from the pre format converter. + if (pDSP->isChannelRoutingAtStart) { + return (mal_uint32)mal_channel_router_read_deinterleaved(&pDSP->channelRouter, frameCount, ppSamplesOut, pUserData); + } else { + return (mal_uint32)mal_format_converter_read_deinterleaved(&pDSP->formatConverterIn, frameCount, ppSamplesOut, pUserData); + } } -mal_result mal_dsp_init(mal_dsp_config* pConfig, mal_dsp_read_proc onRead, void* pUserData, mal_dsp* pDSP) +mal_uint32 mal_dsp__channel_router_on_read_deinterleaved(mal_channel_router* pRouter, mal_uint32 frameCount, void** ppSamplesOut, void* pUserData) { - if (pDSP == NULL) return MAL_INVALID_ARGS; - mal_zero_object(pDSP); - pDSP->config = *pConfig; - pDSP->onRead = onRead; - pDSP->pUserDataForOnRead = pUserData; + (void)pRouter; - if (pDSP->config.cacheSizeInFrames > MAL_SRC_CACHE_SIZE_IN_FRAMES || pDSP->config.cacheSizeInFrames == 0) { - pDSP->config.cacheSizeInFrames = MAL_SRC_CACHE_SIZE_IN_FRAMES; + mal_dsp_callback_data* pData = (mal_dsp_callback_data*)pUserData; + mal_assert(pData != NULL); + + mal_dsp* pDSP = pData->pDSP; + mal_assert(pDSP != NULL); + + // If the channel routing stage is at the front of the pipeline we read from the pre format converter. Otherwise we read from the sample rate converter. + if (pDSP->isChannelRoutingAtStart) { + return (mal_uint32)mal_format_converter_read_deinterleaved(&pDSP->formatConverterIn, frameCount, ppSamplesOut, pUserData); + } else { + if (pDSP->isSRCRequired) { + return (mal_uint32)mal_src_read_deinterleaved_ex(&pDSP->src, frameCount, ppSamplesOut, pData->flush, pUserData); + } else { + return (mal_uint32)mal_format_converter_read_deinterleaved(&pDSP->formatConverterIn, frameCount, ppSamplesOut, pUserData); + } + } +} + +mal_result mal_dsp_init(const mal_dsp_config* pConfig, mal_dsp* pDSP) +{ + if (pDSP == NULL) { + return MAL_INVALID_ARGS; } - if (pConfig->sampleRateIn != pConfig->sampleRateOut) { - pDSP->isSRCRequired = MAL_TRUE; + mal_zero_object(pDSP); + pDSP->onRead = pConfig->onRead; + pDSP->pUserData = pConfig->pUserData; - mal_src_config srcConfig; - srcConfig.sampleRateIn = pConfig->sampleRateIn; - srcConfig.sampleRateOut = pConfig->sampleRateOut; - srcConfig.formatIn = pConfig->formatIn; - srcConfig.formatOut = mal_format_f32; - srcConfig.channels = pConfig->channelsIn; - srcConfig.algorithm = mal_src_algorithm_linear; - srcConfig.cacheSizeInFrames = pConfig->cacheSizeInFrames; - mal_result result = mal_src_init(&srcConfig, mal_dsp__src_on_read, pDSP, &pDSP->src); + // This is generally the pipeline used for data conversion. Note that this can actually change which is explained later. + // + // Pre Format Conversion -> Sample Rate Conversion -> Channel Routing -> Post Format Conversion + // + // Pre Format Conversion + // --------------------- + // This is where the sample data is converted to a format that's usable by the later stages in the pipeline. Input data + // is converted to deinterleaved floating-point. + // + // Channel Routing + // --------------- + // Channel routing is where stereo is converted to 5.1, mono is converted to stereo, etc. This stage depends on the + // pre format conversion stage. + // + // Sample Rate Conversion + // ---------------------- + // Sample rate conversion depends on the pre format conversion stage and as the name implies performs sample rate conversion. + // + // Post Format Conversion + // ---------------------- + // This stage is where our deinterleaved floating-point data from the previous stages are converted to the requested output + // format. + // + // + // Optimizations + // ------------- + // Sometimes the conversion pipeline is rearranged for efficiency. The first obvious optimization is to eliminate unnecessary + // stages in the pipeline. When no channel routing nor sample rate conversion is necessary, the entire pipeline is optimized + // down to just this: + // + // Post Format Conversion + // + // When sample rate conversion is not unnecessary: + // + // Pre Format Conversion -> Channel Routing -> Post Format Conversion + // + // When channel routing is unnecessary: + // + // Pre Format Conversion -> Sample Rate Conversion -> Post Format Conversion + // + // A slightly less obvious optimization is used depending on whether or not we are increasing or decreasing the number of + // channels. Because everything in the pipeline works on a per-channel basis, the efficiency of the pipeline is directly + // proportionate to the number of channels that need to be processed. Therefore, it's can be more efficient to move the + // channel conversion stage to an earlier or later stage. When the channel count is being reduced, we move the channel + // conversion stage to the start of the pipeline so that later stages can work on a smaller number of channels at a time. + // Otherwise, we move the channel conversion stage to the end of the pipeline. When reducing the channel count, the pipeline + // will look like this: + // + // Pre Format Conversion -> Channel Routing -> Sample Rate Conversion -> Post Format Conversion + // + // Notice how the Channel Routing and Sample Rate Conversion stages are swapped so that the SRC stage has less data to process. + + // First we need to determin what's required and what's not. + if (pConfig->sampleRateIn != pConfig->sampleRateOut || pConfig->allowDynamicSampleRate) { + pDSP->isSRCRequired = MAL_TRUE; + } + if (pConfig->channelsIn != pConfig->channelsOut || !mal_channel_map_equal(pConfig->channelsIn, pConfig->channelMapIn, pConfig->channelMapOut)) { + pDSP->isChannelRoutingRequired = MAL_TRUE; + } + + // If neither a sample rate conversion nor channel conversion is necessary we can skip the pre format conversion. + if (!pDSP->isSRCRequired && !pDSP->isChannelRoutingRequired) { + // We don't need a pre format conversion stage, but we may still need a post format conversion stage. + if (pConfig->formatIn != pConfig->formatOut) { + pDSP->isPostFormatConversionRequired = MAL_TRUE; + } + } else { + pDSP->isPreFormatConversionRequired = MAL_TRUE; + pDSP->isPostFormatConversionRequired = MAL_TRUE; + } + + // Use a passthrough if none of the stages are being used. + if (!pDSP->isPreFormatConversionRequired && !pDSP->isPostFormatConversionRequired && !pDSP->isChannelRoutingRequired && !pDSP->isSRCRequired) { + pDSP->isPassthrough = MAL_TRUE; + } + + // Move the channel conversion stage to the start of the pipeline if we are reducing the channel count. + if (pConfig->channelsOut < pConfig->channelsIn) { + pDSP->isChannelRoutingAtStart = MAL_TRUE; + } + + + // We always initialize every stage of the pipeline regardless of whether or not the stage is used because it simplifies + // a few things when it comes to dynamically changing properties post-initialization. + mal_result result = MAL_SUCCESS; + + // Pre format conversion. + { + mal_format_converter_config preFormatConverterConfig; + mal_zero_object(&preFormatConverterConfig); + preFormatConverterConfig.formatIn = pConfig->formatIn; + preFormatConverterConfig.formatOut = mal_format_f32; + preFormatConverterConfig.channels = pConfig->channelsIn; + preFormatConverterConfig.streamFormatIn = mal_stream_format_pcm; + preFormatConverterConfig.streamFormatOut = mal_stream_format_pcm; + preFormatConverterConfig.onRead = mal_dsp__pre_format_converter_on_read; + preFormatConverterConfig.pUserData = pDSP; + result = mal_format_converter_init(&preFormatConverterConfig, &pDSP->formatConverterIn); if (result != MAL_SUCCESS) { return result; } } - - - pDSP->isChannelMappingRequired = MAL_FALSE; - if (pConfig->channelMapIn[0] != MAL_CHANNEL_NONE && pConfig->channelMapOut[0] != MAL_CHANNEL_NONE) { // <-- Channel mapping will be ignored if the first channel map is MAL_CHANNEL_NONE. - // When using channel mapping we need to figure out a shuffling table. The first thing to do is convert the input channel map - // so that it contains the same number of channels as the output channel count. - mal_uint32 iChannel; - mal_uint32 channelsMin = mal_min(pConfig->channelsIn, pConfig->channelsOut); - for (iChannel = 0; iChannel < channelsMin; ++iChannel) { - pDSP->channelMapInPostMix[iChannel] = pConfig->channelMapIn[iChannel]; + // Post format conversion. The exact configuration for this depends on whether or not we are reading data directly from the client + // or from an earlier stage in the pipeline. + { + mal_format_converter_config postFormatConverterConfig; + mal_zero_object(&postFormatConverterConfig); + postFormatConverterConfig.formatIn = pConfig->formatIn; + postFormatConverterConfig.formatOut = pConfig->formatOut; + postFormatConverterConfig.channels = pConfig->channelsOut; + postFormatConverterConfig.streamFormatIn = mal_stream_format_pcm; + postFormatConverterConfig.streamFormatOut = mal_stream_format_pcm; + postFormatConverterConfig.pUserData = pDSP; + if (pDSP->isPreFormatConversionRequired) { + postFormatConverterConfig.onReadDeinterleaved = mal_dsp__post_format_converter_on_read_deinterleaved; + postFormatConverterConfig.formatIn = mal_format_f32; + } else { + postFormatConverterConfig.onRead = mal_dsp__post_format_converter_on_read; } - // Any excess channels need to be filled with the relevant channels from the output channel map. Currently we're justing filling it with - // the first channels that are not present in the input channel map. - if (pConfig->channelsOut > pConfig->channelsIn) { - for (iChannel = pConfig->channelsIn; iChannel < pConfig->channelsOut; ++iChannel) { - mal_uint8 newChannel = MAL_CHANNEL_NONE; - for (mal_uint32 iChannelOut = 0; iChannelOut < pConfig->channelsOut; ++iChannelOut) { - mal_bool32 exists = MAL_FALSE; - for (mal_uint32 iChannelIn = 0; iChannelIn < pConfig->channelsIn; ++iChannelIn) { - if (pConfig->channelMapOut[iChannelOut] == pConfig->channelMapIn[iChannelIn]) { - exists = MAL_TRUE; - break; - } - } - - if (!exists) { - newChannel = pConfig->channelMapOut[iChannelOut]; - break; - } - } - - pDSP->channelMapInPostMix[iChannel] = newChannel; - } - } - - // We only need to do a channel mapping if the map after mixing is different to the final output map. - for (iChannel = 0; iChannel < pConfig->channelsOut; ++iChannel) { - if (pDSP->channelMapInPostMix[iChannel] != pConfig->channelMapOut[iChannel]) { - pDSP->isChannelMappingRequired = MAL_TRUE; - break; - } - } - - // Now we need to create the shuffling table. - if (pDSP->isChannelMappingRequired) { - for (mal_uint32 iChannelIn = 0; iChannelIn < pConfig->channelsOut; ++iChannelIn) { - for (mal_uint32 iChannelOut = 0; iChannelOut < pConfig->channelsOut; ++iChannelOut) { - if (pDSP->channelMapInPostMix[iChannelOut] == pConfig->channelMapOut[iChannelIn]) { - pDSP->channelShuffleTable[iChannelOut] = (mal_uint8)iChannelIn; - } - } - } + result = mal_format_converter_init(&postFormatConverterConfig, &pDSP->formatConverterOut); + if (result != MAL_SUCCESS) { + return result; } } - if (pConfig->formatIn == pConfig->formatOut && pConfig->channelsIn == pConfig->channelsOut && pConfig->sampleRateIn == pConfig->sampleRateOut && !pDSP->isChannelMappingRequired) { - pDSP->isPassthrough = MAL_TRUE; - } else { - pDSP->isPassthrough = MAL_FALSE; + // SRC + { + mal_src_config srcConfig; + mal_zero_object(&srcConfig); + srcConfig.sampleRateIn = pConfig->sampleRateIn; + srcConfig.sampleRateOut = pConfig->sampleRateOut; + srcConfig.channels = (pConfig->channelsIn < pConfig->channelsOut) ? pConfig->channelsIn : pConfig->channelsOut; + srcConfig.algorithm = pConfig->srcAlgorithm; + srcConfig.onReadDeinterleaved = mal_dsp__src_on_read_deinterleaved; + srcConfig.pUserData = pDSP; + result = mal_src_init(&srcConfig, &pDSP->src); + if (result != MAL_SUCCESS) { + return result; + } + } + + // Channel conversion + { + mal_channel_router_config routerConfig = mal_channel_router_config_init( + pConfig->channelsIn, + pConfig->channelMapIn, + pConfig->channelsOut, + pConfig->channelMapOut, + pConfig->channelMixMode, + mal_dsp__channel_router_on_read_deinterleaved, + pDSP); + result = mal_channel_router_init(&routerConfig, &pDSP->channelRouter); + if (result != MAL_SUCCESS) { + return result; + } } return MAL_SUCCESS; } + +mal_result mal_dsp_refresh_sample_rate(mal_dsp* pDSP) +{ + // The SRC stage will already have been initialized so we can just set it there. + mal_src_set_input_sample_rate(&pDSP->src, pDSP->src.config.sampleRateIn); + mal_src_set_output_sample_rate(&pDSP->src, pDSP->src.config.sampleRateOut); + + return MAL_SUCCESS; +} + +mal_result mal_dsp_set_input_sample_rate(mal_dsp* pDSP, mal_uint32 sampleRateIn) +{ + if (pDSP == NULL) { + return MAL_INVALID_ARGS; + } + + // Must have a sample rate of > 0. + if (sampleRateIn == 0) { + return MAL_INVALID_ARGS; + } + + // Must have been initialized with allowDynamicSampleRate. + if (!pDSP->isDynamicSampleRateAllowed) { + return MAL_INVALID_OPERATION; + } + + mal_atomic_exchange_32(&pDSP->src.config.sampleRateIn, sampleRateIn); + return mal_dsp_refresh_sample_rate(pDSP); +} + mal_result mal_dsp_set_output_sample_rate(mal_dsp* pDSP, mal_uint32 sampleRateOut) { - if (pDSP == NULL) return MAL_INVALID_ARGS; + 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; - } + // Must have been initialized with allowDynamicSampleRate. + if (!pDSP->isDynamicSampleRateAllowed) { + return MAL_INVALID_OPERATION; } - // 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_atomic_exchange_32(&pDSP->src.config.sampleRateOut, sampleRateOut); + return mal_dsp_refresh_sample_rate(pDSP); } -mal_uint32 mal_dsp_read_frames(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut) +mal_uint64 mal_dsp_read(mal_dsp* pDSP, mal_uint64 frameCount, void* pFramesOut, void* pUserData) { - return mal_dsp_read_frames_ex(pDSP, frameCount, pFramesOut, MAL_FALSE); + return mal_dsp_read_ex(pDSP, frameCount, pFramesOut, MAL_FALSE, pUserData); } -mal_uint32 mal_dsp_read_frames_ex(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush) +mal_uint64 mal_dsp_read_ex(mal_dsp* pDSP, mal_uint64 frameCount, void* pFramesOut, mal_bool32 flush, void* pUserData) { if (pDSP == NULL || pFramesOut == NULL) return 0; // Fast path. if (pDSP->isPassthrough) { - return pDSP->onRead(pDSP, frameCount, pFramesOut, pDSP->pUserDataForOnRead); - } - - - // Slower path - where the real work is done. - mal_uint8 pFrames[2][MAL_MAX_CHANNELS * 512 * MAL_MAX_SAMPLE_SIZE_IN_BYTES]; - mal_format pFramesFormat[2]; - mal_uint32 iFrames = 0; // <-- Used as an index into pFrames and cycles between 0 and 1. - - mal_uint32 totalFramesRead = 0; - while (frameCount > 0) { - iFrames = 0; - - mal_uint32 framesToRead = mal_countof(pFrames[0]) / (mal_max(pDSP->config.channelsIn, pDSP->config.channelsOut) * MAL_MAX_SAMPLE_SIZE_IN_BYTES); - if (framesToRead > frameCount) { - framesToRead = frameCount; - } - - // The initial filling of sample data depends on whether or not we are using SRC. - mal_uint32 framesRead = 0; - if (pDSP->isSRCRequired) { - framesRead = mal_src_read_frames_ex(&pDSP->src, framesToRead, pFrames[iFrames], flush); - pFramesFormat[iFrames] = pDSP->src.config.formatOut; // Should always be f32. + if (frameCount <= 0xFFFFFFFF) { + return (mal_uint32)pDSP->onRead(pDSP, (mal_uint32)frameCount, pFramesOut, pUserData); } else { - framesRead = pDSP->onRead(pDSP, framesToRead, pFrames[iFrames], pDSP->pUserDataForOnRead); - pFramesFormat[iFrames] = pDSP->config.formatIn; - } + mal_uint8* pNextFramesOut = (mal_uint8*)pFramesOut; - if (framesRead == 0) { - break; - } + mal_uint64 totalFramesRead = 0; + while (totalFramesRead < frameCount) { + mal_uint64 framesRemaining = (frameCount - totalFramesRead); + mal_uint64 framesToReadRightNow = framesRemaining; + if (framesToReadRightNow > 0xFFFFFFFF) { + framesToReadRightNow = 0xFFFFFFFF; + } + mal_uint32 framesRead = pDSP->onRead(pDSP, (mal_uint32)framesToReadRightNow, pNextFramesOut, pUserData); + if (framesRead == 0) { + break; + } - // Channel mixing. The input format must be in f32 which may require a conversion. - if (pDSP->config.channelsIn != pDSP->config.channelsOut) { - if (pFramesFormat[iFrames] != mal_format_f32) { - mal_pcm_convert(pFrames[(iFrames + 1) % 2], mal_format_f32, pFrames[iFrames], pDSP->config.formatIn, framesRead * pDSP->config.channelsIn); - iFrames = (iFrames + 1) % 2; - pFramesFormat[iFrames] = mal_format_f32; + pNextFramesOut += framesRead * pDSP->channelRouter.config.channelsOut * mal_get_bytes_per_sample(pDSP->formatConverterOut.config.formatOut); + totalFramesRead += framesRead; } - mal_dsp_mix_channels((float*)(pFrames[(iFrames + 1) % 2]), pDSP->config.channelsOut, pDSP->config.channelMapOut, (const float*)(pFrames[iFrames]), pDSP->config.channelsIn, pDSP->config.channelMapIn, framesRead, mal_channel_mix_mode_blend); - iFrames = (iFrames + 1) % 2; - pFramesFormat[iFrames] = mal_format_f32; + return totalFramesRead; } - - - // Channel mapping. - if (pDSP->isChannelMappingRequired) { - for (mal_uint32 i = 0; i < framesRead; ++i) { - mal_rearrange_channels(pFrames[iFrames] + (i * pDSP->config.channelsOut * mal_get_sample_size_in_bytes(pFramesFormat[iFrames])), pDSP->config.channelsOut, pDSP->channelShuffleTable, pFramesFormat[iFrames]); - } - } - - - // Final conversion to output format. - mal_pcm_convert(pFramesOut, pDSP->config.formatOut, pFrames[iFrames], pFramesFormat[iFrames], framesRead * pDSP->config.channelsOut); - - pFramesOut = (mal_uint8*)pFramesOut + (framesRead * pDSP->config.channelsOut * mal_get_sample_size_in_bytes(pDSP->config.formatOut)); - frameCount -= framesRead; - totalFramesRead += framesRead; } - return totalFramesRead; + // Slower path. The real is done here. To do this all we need to do is read from the last stage in the pipeline. + mal_assert(pDSP->isPostFormatConversionRequired == MAL_TRUE); + + mal_dsp_callback_data data; + data.pDSP = pDSP; + data.flush = flush; + data.pUserDataForClient = pUserData; + return mal_format_converter_read(&pDSP->formatConverterOut, frameCount, pFramesOut, &data); } -mal_uint32 mal_calculate_frame_count_after_src(mal_uint32 sampleRateOut, mal_uint32 sampleRateIn, mal_uint32 frameCountIn) -{ - double srcRatio = (double)sampleRateOut / sampleRateIn; - double frameCountOutF = frameCountIn * srcRatio; - - mal_uint32 frameCountOut = (mal_uint32)frameCountOutF; - - // If the output frame count is fractional, make sure we add an extra frame to ensure there's enough room for that last sample. - if ((frameCountOutF - frameCountOut) > 0.0) { - frameCountOut += 1; - } - - return frameCountOut; -} - typedef struct { const void* pDataIn; mal_format formatIn; mal_uint32 channelsIn; - mal_uint32 totalFrameCount; - mal_uint32 iNextFrame; + mal_uint64 totalFrameCount; + mal_uint64 iNextFrame; } mal_convert_frames__data; mal_uint32 mal_convert_frames__on_read(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut, void* pUserData) @@ -11152,25 +18952,73 @@ mal_uint32 mal_convert_frames__on_read(mal_dsp* pDSP, mal_uint32 frameCount, voi mal_assert(pData->totalFrameCount >= pData->iNextFrame); mal_uint32 framesToRead = frameCount; - mal_uint32 framesRemaining = (pData->totalFrameCount - pData->iNextFrame); + mal_uint64 framesRemaining = (pData->totalFrameCount - pData->iNextFrame); if (framesToRead > framesRemaining) { - framesToRead = framesRemaining; + framesToRead = (mal_uint32)framesRemaining; } - mal_uint32 frameSizeInBytes = mal_get_sample_size_in_bytes(pData->formatIn) * pData->channelsIn; + mal_uint32 frameSizeInBytes = mal_get_bytes_per_sample(pData->formatIn) * pData->channelsIn; mal_copy_memory(pFramesOut, (const mal_uint8*)pData->pDataIn + (frameSizeInBytes * pData->iNextFrame), frameSizeInBytes * framesToRead); pData->iNextFrame += framesToRead; return framesToRead; } -mal_uint32 mal_convert_frames(void* pOut, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, const void* pIn, mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_uint32 frameCountIn) +mal_dsp_config mal_dsp_config_init_new() +{ + mal_dsp_config config; + mal_zero_object(&config); + + return config; +} + +mal_dsp_config mal_dsp_config_init(mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, mal_dsp_read_proc onRead, void* pUserData) +{ + return mal_dsp_config_init_ex(formatIn, channelsIn, sampleRateIn, NULL, formatOut, channelsOut, sampleRateOut, NULL, onRead, pUserData); +} + +mal_dsp_config mal_dsp_config_init_ex(mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, mal_channel channelMapOut[MAL_MAX_CHANNELS], mal_dsp_read_proc onRead, void* pUserData) +{ + mal_dsp_config config; + mal_zero_object(&config); + config.formatIn = formatIn; + config.channelsIn = channelsIn; + config.sampleRateIn = sampleRateIn; + config.formatOut = formatOut; + config.channelsOut = channelsOut; + config.sampleRateOut = sampleRateOut; + if (channelMapIn != NULL) { + mal_copy_memory(config.channelMapIn, channelMapIn, sizeof(config.channelMapIn)); + } + if (channelMapOut != NULL) { + mal_copy_memory(config.channelMapOut, channelMapOut, sizeof(config.channelMapOut)); + } + config.onRead = onRead; + config.pUserData = pUserData; + + return config; +} + + + +mal_uint64 mal_convert_frames(void* pOut, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, const void* pIn, mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_uint64 frameCountIn) +{ + mal_channel channelMapOut[MAL_MAX_CHANNELS]; + mal_get_standard_channel_map(mal_standard_channel_map_default, channelsOut, channelMapOut); + + mal_channel channelMapIn[MAL_MAX_CHANNELS]; + mal_get_standard_channel_map(mal_standard_channel_map_default, channelsIn, channelMapIn); + + return mal_convert_frames_ex(pOut, formatOut, channelsOut, sampleRateOut, channelMapOut, pIn, formatIn, channelsIn, sampleRateIn, channelMapIn, frameCountIn); +} + +mal_uint64 mal_convert_frames_ex(void* pOut, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, mal_channel channelMapOut[MAL_MAX_CHANNELS], const void* pIn, mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_uint64 frameCountIn) { if (frameCountIn == 0) { return 0; } - mal_uint32 frameCountOut = mal_calculate_frame_count_after_src(sampleRateOut, sampleRateIn, frameCountIn); + mal_uint64 frameCountOut = mal_calculate_frame_count_after_src(sampleRateOut, sampleRateIn, frameCountIn); if (pOut == NULL) { return frameCountOut; } @@ -11184,33 +19032,34 @@ mal_uint32 mal_convert_frames(void* pOut, mal_format formatOut, mal_uint32 chann mal_dsp_config config; mal_zero_object(&config); + config.formatIn = formatIn; config.channelsIn = channelsIn; config.sampleRateIn = sampleRateIn; + if (channelMapIn != NULL) { + mal_channel_map_copy(config.channelMapIn, channelMapIn, channelsIn); + } else { + mal_get_standard_channel_map(mal_standard_channel_map_default, config.channelsIn, config.channelMapIn); + } + config.formatOut = formatOut; config.channelsOut = channelsOut; config.sampleRateOut = sampleRateOut; + if (channelMapOut != NULL) { + mal_channel_map_copy(config.channelMapOut, channelMapOut, channelsOut); + } else { + mal_get_standard_channel_map(mal_standard_channel_map_default, config.channelsOut, config.channelMapOut); + } + + config.onRead = mal_convert_frames__on_read; + config.pUserData = &data; mal_dsp dsp; - if (mal_dsp_init(&config, mal_convert_frames__on_read, &data, &dsp) != MAL_SUCCESS) { + if (mal_dsp_init(&config, &dsp) != MAL_SUCCESS) { return 0; } - return mal_dsp_read_frames_ex(&dsp, frameCountOut, pOut, MAL_TRUE); -} - -mal_dsp_config mal_dsp_config_init(mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut) -{ - mal_dsp_config config; - mal_zero_object(&config); - config.formatIn = formatIn; - config.channelsIn = channelsIn; - config.sampleRateIn = sampleRateIn; - config.formatOut = formatOut; - config.channelsOut = channelsOut; - config.sampleRateOut = sampleRateOut; - - return config; + return mal_dsp_read_ex(&dsp, frameCountOut, pOut, MAL_TRUE, dsp.pUserData); } @@ -11224,23 +19073,47 @@ mal_dsp_config mal_dsp_config_init(mal_format formatIn, mal_uint32 channelsIn, m ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void* mal_aligned_malloc(size_t sz, size_t alignment) +{ + if (alignment == 0) { + return 0; + } + + size_t extraBytes = alignment-1 + sizeof(void*); + + void* pUnaligned = mal_malloc(sz + extraBytes); + if (pUnaligned == NULL) { + return NULL; + } + + void* pAligned = (void*)(((mal_uintptr)pUnaligned + extraBytes) & ~((mal_uintptr)(alignment-1))); + ((void**)pAligned)[-1] = pUnaligned; + + return pAligned; +} + +void mal_aligned_free(void* p) +{ + mal_free(((void**)p)[-1]); +} + const char* mal_get_backend_name(mal_backend backend) { switch (backend) { - case mal_backend_null: return "Null"; - case mal_backend_wasapi: return "WASAPI"; - case mal_backend_dsound: return "DirectSound"; - case mal_backend_winmm: return "WinMM"; - case mal_backend_alsa: return "ALSA"; - //case mal_backend_pulse: return "PulseAudio"; - //case mal_backend_jack: return "JACK"; - //case mal_backend_coreaudio: return "Core Audio"; - case mal_backend_oss: return "OSS"; - case mal_backend_opensl: return "OpenSL|ES"; - case mal_backend_openal: return "OpenAL"; - case mal_backend_sdl: return "SDL"; - default: return "Unknown"; + case mal_backend_null: return "Null"; + case mal_backend_wasapi: return "WASAPI"; + case mal_backend_dsound: return "DirectSound"; + case mal_backend_winmm: return "WinMM"; + case mal_backend_alsa: return "ALSA"; + case mal_backend_pulseaudio: return "PulseAudio"; + case mal_backend_jack: return "JACK"; + //case mal_backend_coreaudio: return "Core Audio"; + case mal_backend_oss: return "OSS"; + case mal_backend_opensl: return "OpenSL|ES"; + case mal_backend_openal: return "OpenAL"; + case mal_backend_sdl: return "SDL"; + default: return "Unknown"; } } @@ -11267,267 +19140,1270 @@ void mal_blend_f32(float* pOut, float* pInA, float* pInB, float factor, mal_uint -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // -// -// -// AUTO-GENERATED -// -// +// DECODING // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#ifndef MAL_NO_DECODING -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// FORMAT CONVERSION -// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -void mal_pcm_u8_to_s16(short* pOut, const unsigned char* pIn, unsigned int count) +mal_decoder_config mal_decoder_config_init(mal_format outputFormat, mal_uint32 outputChannels, mal_uint32 outputSampleRate) { - int r; - for (unsigned int i = 0; i < count; ++i) { - int x = pIn[i]; - r = x - 128; - r = r << 8; - pOut[i] = (short)r; + mal_decoder_config config; + mal_zero_object(&config); + config.outputFormat = outputFormat; + config.outputChannels = outputChannels; + config.outputSampleRate = outputSampleRate; + mal_get_standard_channel_map(mal_standard_channel_map_default, config.outputChannels, config.outputChannelMap); + + return config; +} + +mal_decoder_config mal_decoder_config_init_copy(const mal_decoder_config* pConfig) +{ + mal_decoder_config config; + if (pConfig != NULL) { + config = *pConfig; + } else { + mal_zero_object(&config); + } + + return config; +} + +mal_result mal_decoder__init_dsp(mal_decoder* pDecoder, const mal_decoder_config* pConfig, mal_dsp_read_proc onRead) +{ + mal_assert(pDecoder != NULL); + + // Output format. + if (pConfig->outputFormat == mal_format_unknown) { + pDecoder->outputFormat = pDecoder->internalFormat; + } else { + pDecoder->outputFormat = pConfig->outputFormat; + } + + if (pConfig->outputChannels == 0) { + pDecoder->outputChannels = pDecoder->internalChannels; + } else { + pDecoder->outputChannels = pConfig->outputChannels; + } + + if (pConfig->outputSampleRate == 0) { + pDecoder->outputSampleRate = pDecoder->internalSampleRate; + } else { + pDecoder->outputSampleRate = pConfig->outputSampleRate; + } + + if (mal_channel_map_blank(pDecoder->outputChannels, pConfig->outputChannelMap)) { + mal_get_standard_channel_map(mal_standard_channel_map_default, pDecoder->outputChannels, pDecoder->outputChannelMap); + } else { + mal_copy_memory(pDecoder->outputChannelMap, pConfig->outputChannelMap, sizeof(pConfig->outputChannelMap)); + } + + + // DSP. + mal_dsp_config dspConfig = mal_dsp_config_init_ex( + pDecoder->internalFormat, pDecoder->internalChannels, pDecoder->internalSampleRate, pDecoder->internalChannelMap, + pDecoder->outputFormat, pDecoder->outputChannels, pDecoder->outputSampleRate, pDecoder->outputChannelMap, + onRead, pDecoder); + return mal_dsp_init(&dspConfig, &pDecoder->dsp); +} + +// WAV +#ifdef dr_wav_h +#define MAL_HAS_WAV + +size_t mal_decoder_internal_on_read__wav(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + mal_decoder* pDecoder = (mal_decoder*)pUserData; + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->onRead != NULL); + + return pDecoder->onRead(pDecoder, pBufferOut, bytesToRead); +} + +drwav_bool32 mal_decoder_internal_on_seek__wav(void* pUserData, int offset, drwav_seek_origin origin) +{ + mal_decoder* pDecoder = (mal_decoder*)pUserData; + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->onSeek != NULL); + + return pDecoder->onSeek(pDecoder, offset, (origin == drwav_seek_origin_start) ? mal_seek_origin_start : mal_seek_origin_current); +} + +mal_result mal_decoder_internal_on_seek_to_frame__wav(mal_decoder* pDecoder, mal_uint64 frameIndex) +{ + drwav* pWav = (drwav*)pDecoder->pInternalDecoder; + mal_assert(pWav != NULL); + + drwav_bool32 result = drwav_seek_to_sample(pWav, frameIndex*pWav->channels); + if (result) { + return MAL_SUCCESS; + } else { + return MAL_ERROR; } } -void mal_pcm_u8_to_s24(void* pOut, const unsigned char* pIn, unsigned int count) +mal_result mal_decoder_internal_on_uninit__wav(mal_decoder* pDecoder) { - int r; - for (unsigned int i = 0; i < count; ++i) { - int x = pIn[i]; - r = x - 128; - r = r << 16; - ((unsigned char*)pOut)[(i*3)+0] = (unsigned char)(r & 0xFF); ((unsigned char*)pOut)[(i*3)+1] = (unsigned char)((r & 0xFF00) >> 8); ((unsigned char*)pOut)[(i*3)+2] = (unsigned char)((r & 0xFF0000) >> 16); - } + drwav_close((drwav*)pDecoder->pInternalDecoder); + return MAL_SUCCESS; } -void mal_pcm_u8_to_s32(int* pOut, const unsigned char* pIn, unsigned int count) +mal_uint32 mal_decoder_internal_on_read_frames__wav(mal_dsp* pDSP, mal_uint32 frameCount, void* pSamplesOut, void* pUserData) { - int r; - for (unsigned int i = 0; i < count; ++i) { - int x = pIn[i]; - r = x - 128; - r = r << 24; - pOut[i] = (int)r; + (void)pDSP; + + mal_decoder* pDecoder = (mal_decoder*)pUserData; + mal_assert(pDecoder != NULL); + + drwav* pWav = (drwav*)pDecoder->pInternalDecoder; + mal_assert(pWav != NULL); + + switch (pDecoder->internalFormat) { + case mal_format_s16: return (mal_uint32)drwav_read_s16(pWav, frameCount*pDecoder->internalChannels, (drwav_int16*)pSamplesOut) / pDecoder->internalChannels; + case mal_format_s32: return (mal_uint32)drwav_read_s32(pWav, frameCount*pDecoder->internalChannels, (drwav_int32*)pSamplesOut) / pDecoder->internalChannels; + case mal_format_f32: return (mal_uint32)drwav_read_f32(pWav, frameCount*pDecoder->internalChannels, (float*)pSamplesOut) / pDecoder->internalChannels; + default: break; } + + // Should never get here. If we do, it means the internal format was not set correctly at initialization time. + mal_assert(MAL_FALSE); + return 0; } -void mal_pcm_u8_to_f32(float* pOut, const unsigned char* pIn, unsigned int count) +mal_result mal_decoder_init_wav__internal(const mal_decoder_config* pConfig, mal_decoder* pDecoder) { - float r; - for (unsigned int i = 0; i < count; ++i) { - int x = pIn[i]; - r = x * 0.00784313725490196078f; - r = r - 1; - pOut[i] = (float)r; - } -} + mal_assert(pConfig != NULL); + mal_assert(pDecoder != NULL); -void mal_pcm_s16_to_u8(unsigned char* pOut, const short* pIn, unsigned int count) -{ - int r; - for (unsigned int i = 0; i < count; ++i) { - int x = pIn[i]; - r = x >> 8; - r = r + 128; - pOut[i] = (unsigned char)r; + // Try opening the decoder first. + drwav* pWav = drwav_open(mal_decoder_internal_on_read__wav, mal_decoder_internal_on_seek__wav, pDecoder); + if (pWav == NULL) { + return MAL_ERROR; } -} -void mal_pcm_s16_to_s24(void* pOut, const short* pIn, unsigned int count) -{ - int r; - for (unsigned int i = 0; i < count; ++i) { - int x = pIn[i]; - r = x << 8; - ((unsigned char*)pOut)[(i*3)+0] = (unsigned char)(r & 0xFF); ((unsigned char*)pOut)[(i*3)+1] = (unsigned char)((r & 0xFF00) >> 8); ((unsigned char*)pOut)[(i*3)+2] = (unsigned char)((r & 0xFF0000) >> 16); + // If we get here it means we successfully initialized the WAV decoder. We can now initialize the rest of the mal_decoder. + pDecoder->onSeekToFrame = mal_decoder_internal_on_seek_to_frame__wav; + pDecoder->onUninit = mal_decoder_internal_on_uninit__wav; + pDecoder->pInternalDecoder = pWav; + + // Try to be as optimal as possible for the internal format. If mini_al does not support a format we will fall back to f32. + pDecoder->internalFormat = mal_format_unknown; + switch (pWav->translatedFormatTag) { + case DR_WAVE_FORMAT_PCM: + { + if (pWav->bitsPerSample == 8) { + pDecoder->internalFormat = mal_format_s16; + } else if (pWav->bitsPerSample == 16) { + pDecoder->internalFormat = mal_format_s16; + } else if (pWav->bitsPerSample == 32) { + pDecoder->internalFormat = mal_format_s32; + } + } break; + + case DR_WAVE_FORMAT_IEEE_FLOAT: + { + if (pWav->bitsPerSample == 32) { + pDecoder->internalFormat = mal_format_f32; + } + } break; + + case DR_WAVE_FORMAT_ALAW: + case DR_WAVE_FORMAT_MULAW: + case DR_WAVE_FORMAT_ADPCM: + case DR_WAVE_FORMAT_DVI_ADPCM: + { + pDecoder->internalFormat = mal_format_s16; + } break; } -} -void mal_pcm_s16_to_s32(int* pOut, const short* pIn, unsigned int count) -{ - int r; - for (unsigned int i = 0; i < count; ++i) { - int x = pIn[i]; - r = x << 16; - pOut[i] = (int)r; + if (pDecoder->internalFormat == mal_format_unknown) { + pDecoder->internalFormat = mal_format_f32; } -} -void mal_pcm_s16_to_f32(float* pOut, const short* pIn, unsigned int count) -{ - float r; - for (unsigned int i = 0; i < count; ++i) { - int x = pIn[i]; - r = (float)(x + 32768); - r = r * 0.00003051804379339284f; - r = r - 1; - pOut[i] = (float)r; + pDecoder->internalChannels = pWav->channels; + pDecoder->internalSampleRate = pWav->sampleRate; + mal_get_standard_channel_map(mal_standard_channel_map_microsoft, pDecoder->internalChannels, pDecoder->internalChannelMap); + + mal_result result = mal_decoder__init_dsp(pDecoder, pConfig, mal_decoder_internal_on_read_frames__wav); + if (result != MAL_SUCCESS) { + drwav_close(pWav); + return result; } -} -void mal_pcm_s24_to_u8(unsigned char* pOut, const void* pIn, unsigned int count) -{ - int r; - for (unsigned int i = 0; i < count; ++i) { - int x = ((int)(((unsigned int)(((unsigned char*)pIn)[i*3+0]) << 8) | ((unsigned int)(((unsigned char*)pIn)[i*3+1]) << 16) | ((unsigned int)(((unsigned char*)pIn)[i*3+2])) << 24)) >> 8; - r = x >> 16; - r = r + 128; - pOut[i] = (unsigned char)r; - } + return MAL_SUCCESS; } - -void mal_pcm_s24_to_s16(short* pOut, const void* pIn, unsigned int count) -{ - int r; - for (unsigned int i = 0; i < count; ++i) { - int x = ((int)(((unsigned int)(((unsigned char*)pIn)[i*3+0]) << 8) | ((unsigned int)(((unsigned char*)pIn)[i*3+1]) << 16) | ((unsigned int)(((unsigned char*)pIn)[i*3+2])) << 24)) >> 8; - r = x >> 8; - pOut[i] = (short)r; - } -} - -void mal_pcm_s24_to_s32(int* pOut, const void* pIn, unsigned int count) -{ - int r; - for (unsigned int i = 0; i < count; ++i) { - int x = ((int)(((unsigned int)(((unsigned char*)pIn)[i*3+0]) << 8) | ((unsigned int)(((unsigned char*)pIn)[i*3+1]) << 16) | ((unsigned int)(((unsigned char*)pIn)[i*3+2])) << 24)) >> 8; - r = x << 8; - pOut[i] = (int)r; - } -} - -void mal_pcm_s24_to_f32(float* pOut, const void* pIn, unsigned int count) -{ - float r; - for (unsigned int i = 0; i < count; ++i) { - int x = ((int)(((unsigned int)(((unsigned char*)pIn)[i*3+0]) << 8) | ((unsigned int)(((unsigned char*)pIn)[i*3+1]) << 16) | ((unsigned int)(((unsigned char*)pIn)[i*3+2])) << 24)) >> 8; - r = (float)(x + 8388608); - r = r * 0.00000011920929665621f; - r = r - 1; - pOut[i] = (float)r; - } -} - -void mal_pcm_s32_to_u8(unsigned char* pOut, const int* pIn, unsigned int count) -{ - int r; - for (unsigned int i = 0; i < count; ++i) { - int x = pIn[i]; - r = x >> 24; - r = r + 128; - pOut[i] = (unsigned char)r; - } -} - -void mal_pcm_s32_to_s16(short* pOut, const int* pIn, unsigned int count) -{ - int r; - for (unsigned int i = 0; i < count; ++i) { - int x = pIn[i]; - r = x >> 16; - pOut[i] = (short)r; - } -} - -void mal_pcm_s32_to_s24(void* pOut, const int* pIn, unsigned int count) -{ - int r; - for (unsigned int i = 0; i < count; ++i) { - int x = pIn[i]; - r = x >> 8; - ((unsigned char*)pOut)[(i*3)+0] = (unsigned char)(r & 0xFF); ((unsigned char*)pOut)[(i*3)+1] = (unsigned char)((r & 0xFF00) >> 8); ((unsigned char*)pOut)[(i*3)+2] = (unsigned char)((r & 0xFF0000) >> 16); - } -} - -void mal_pcm_s32_to_f32(float* pOut, const int* pIn, unsigned int count) -{ - float r; - for (unsigned int i = 0; i < count; ++i) { - int x = pIn[i]; - double t; - t = (double)(x + 2147483647); - t = t + 1; - t = t * 0.0000000004656612873077392578125; - r = (float)(t - 1); - pOut[i] = (float)r; - } -} - -void mal_pcm_f32_to_u8(unsigned char* pOut, const float* pIn, unsigned int count) -{ - int r; - for (unsigned int i = 0; i < count; ++i) { - float x = pIn[i]; - float c; - c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); - c = c + 1; - r = (int)(c * 127.5f); - pOut[i] = (unsigned char)r; - } -} - -void mal_pcm_f32_to_s16(short* pOut, const float* pIn, unsigned int count) -{ - int r; - for (unsigned int i = 0; i < count; ++i) { - float x = pIn[i]; - float c; - c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); - c = c + 1; - r = (int)(c * 32767.5f); - r = r - 32768; - pOut[i] = (short)r; - } -} - -void mal_pcm_f32_to_s24(void* pOut, const float* pIn, unsigned int count) -{ - int r; - for (unsigned int i = 0; i < count; ++i) { - float x = pIn[i]; - float c; - c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); - c = c + 1; - r = (int)(c * 8388607.5f); - r = r - 8388608; - ((unsigned char*)pOut)[(i*3)+0] = (unsigned char)(r & 0xFF); ((unsigned char*)pOut)[(i*3)+1] = (unsigned char)((r & 0xFF00) >> 8); ((unsigned char*)pOut)[(i*3)+2] = (unsigned char)((r & 0xFF0000) >> 16); - } -} - -void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count) -{ - int r; - for (unsigned int i = 0; i < count; ++i) { - float x = pIn[i]; - float c; - mal_int64 t; - c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); - c = c + 1; - t = (mal_int64)(c * 2147483647.5); - t = t - 2147483647; - r = (int)(t - 1); - pOut[i] = (int)r; - } -} - #endif +// FLAC +#ifdef dr_flac_h +#define MAL_HAS_FLAC + +size_t mal_decoder_internal_on_read__flac(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + mal_decoder* pDecoder = (mal_decoder*)pUserData; + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->onRead != NULL); + + return pDecoder->onRead(pDecoder, pBufferOut, bytesToRead); +} + +drflac_bool32 mal_decoder_internal_on_seek__flac(void* pUserData, int offset, drflac_seek_origin origin) +{ + mal_decoder* pDecoder = (mal_decoder*)pUserData; + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->onSeek != NULL); + + return pDecoder->onSeek(pDecoder, offset, (origin == drflac_seek_origin_start) ? mal_seek_origin_start : mal_seek_origin_current); +} + +mal_result mal_decoder_internal_on_seek_to_frame__flac(mal_decoder* pDecoder, mal_uint64 frameIndex) +{ + drflac* pFlac = (drflac*)pDecoder->pInternalDecoder; + mal_assert(pFlac != NULL); + + drflac_bool32 result = drflac_seek_to_sample(pFlac, frameIndex*pFlac->channels); + if (result) { + return MAL_SUCCESS; + } else { + return MAL_ERROR; + } +} + +mal_result mal_decoder_internal_on_uninit__flac(mal_decoder* pDecoder) +{ + drflac_close((drflac*)pDecoder->pInternalDecoder); + return MAL_SUCCESS; +} + +mal_uint32 mal_decoder_internal_on_read_frames__flac(mal_dsp* pDSP, mal_uint32 frameCount, void* pSamplesOut, void* pUserData) +{ + (void)pDSP; + + mal_decoder* pDecoder = (mal_decoder*)pUserData; + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->internalFormat == mal_format_s32); + + drflac* pFlac = (drflac*)pDecoder->pInternalDecoder; + mal_assert(pFlac != NULL); + + return (mal_uint32)drflac_read_s32(pFlac, frameCount*pDecoder->internalChannels, (drflac_int32*)pSamplesOut) / pDecoder->internalChannels; +} + +mal_result mal_decoder_init_flac__internal(const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_assert(pConfig != NULL); + mal_assert(pDecoder != NULL); + + // Try opening the decoder first. + drflac* pFlac = drflac_open(mal_decoder_internal_on_read__flac, mal_decoder_internal_on_seek__flac, pDecoder); + if (pFlac == NULL) { + return MAL_ERROR; + } + + // If we get here it means we successfully initialized the FLAC decoder. We can now initialize the rest of the mal_decoder. + pDecoder->onSeekToFrame = mal_decoder_internal_on_seek_to_frame__flac; + pDecoder->onUninit = mal_decoder_internal_on_uninit__flac; + pDecoder->pInternalDecoder = pFlac; + + // The internal format is always s32. + pDecoder->internalFormat = mal_format_s32; + pDecoder->internalChannels = pFlac->channels; + pDecoder->internalSampleRate = pFlac->sampleRate; + mal_get_standard_channel_map(mal_standard_channel_map_flac, pDecoder->internalChannels, pDecoder->internalChannelMap); + + mal_result result = mal_decoder__init_dsp(pDecoder, pConfig, mal_decoder_internal_on_read_frames__flac); + if (result != MAL_SUCCESS) { + drflac_close(pFlac); + return result; + } + + return MAL_SUCCESS; +} +#endif + +// Vorbis +#ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H +#define MAL_HAS_VORBIS + +// The size in bytes of each chunk of data to read from the Vorbis stream. +#define MAL_VORBIS_DATA_CHUNK_SIZE 4096 + +typedef struct +{ + stb_vorbis* pInternalVorbis; + mal_uint8* pData; + size_t dataSize; + size_t dataCapacity; + mal_uint32 framesConsumed; // The number of frames consumed in ppPacketData. + mal_uint32 framesRemaining; // The number of frames remaining in ppPacketData. + float** ppPacketData; +} mal_vorbis_decoder; + +mal_uint32 mal_vorbis_decoder_read(mal_vorbis_decoder* pVorbis, mal_decoder* pDecoder, mal_uint32 frameCount, void* pSamplesOut) +{ + mal_assert(pVorbis != NULL); + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->onRead != NULL); + mal_assert(pDecoder->onSeek != NULL); + + float* pSamplesOutF = (float*)pSamplesOut; + + mal_uint32 totalFramesRead = 0; + while (frameCount > 0) { + // Read from the in-memory buffer first. + while (pVorbis->framesRemaining > 0 && frameCount > 0) { + for (mal_uint32 iChannel = 0; iChannel < pDecoder->internalChannels; ++iChannel) { + pSamplesOutF[0] = pVorbis->ppPacketData[iChannel][pVorbis->framesConsumed]; + pSamplesOutF += 1; + } + + pVorbis->framesConsumed += 1; + pVorbis->framesRemaining -= 1; + frameCount -= 1; + totalFramesRead += 1; + } + + if (frameCount == 0) { + break; + } + + mal_assert(pVorbis->framesRemaining == 0); + + // We've run out of cached frames, so decode the next packet and continue iteration. + do + { + if (pVorbis->dataSize > INT_MAX) { + break; // Too big. + } + + int samplesRead = 0; + int consumedDataSize = stb_vorbis_decode_frame_pushdata(pVorbis->pInternalVorbis, pVorbis->pData, (int)pVorbis->dataSize, NULL, (float***)&pVorbis->ppPacketData, &samplesRead); + if (consumedDataSize != 0) { + size_t leftoverDataSize = (pVorbis->dataSize - (size_t)consumedDataSize); + for (size_t i = 0; i < leftoverDataSize; ++i) { + pVorbis->pData[i] = pVorbis->pData[i + consumedDataSize]; + } + + pVorbis->dataSize = leftoverDataSize; + pVorbis->framesConsumed = 0; + pVorbis->framesRemaining = samplesRead; + break; + } else { + // Need more data. If there's any room in the existing buffer allocation fill that first. Otherwise expand. + if (pVorbis->dataCapacity == pVorbis->dataSize) { + // No room. Expand. + pVorbis->dataCapacity += MAL_VORBIS_DATA_CHUNK_SIZE; + mal_uint8* pNewData = (mal_uint8*)mal_realloc(pVorbis->pData, pVorbis->dataCapacity); + if (pNewData == NULL) { + return totalFramesRead; // Out of memory. + } + + pVorbis->pData = pNewData; + } + + // Fill in a chunk. + size_t bytesRead = pDecoder->onRead(pDecoder, pVorbis->pData + pVorbis->dataSize, (pVorbis->dataCapacity - pVorbis->dataSize)); + if (bytesRead == 0) { + return totalFramesRead; // Error reading more data. + } + + pVorbis->dataSize += bytesRead; + } + } while (MAL_TRUE); + } + + return totalFramesRead; +} + +mal_result mal_vorbis_decoder_seek_to_frame(mal_vorbis_decoder* pVorbis, mal_decoder* pDecoder, mal_uint64 frameIndex) +{ + mal_assert(pVorbis != NULL); + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->onRead != NULL); + mal_assert(pDecoder->onSeek != NULL); + + // This is terribly inefficient because stb_vorbis does not have a good seeking solution with it's push API. Currently this just performs + // a full decode right from the start of the stream. Later on I'll need to write a layer that goes through all of the Ogg pages until we + // find the one containing the sample we need. Then we know exactly where to seek for stb_vorbis. + if (!pDecoder->onSeek(pDecoder, 0, mal_seek_origin_start)) { + return MAL_ERROR; + } + + stb_vorbis_flush_pushdata(pVorbis->pInternalVorbis); + pVorbis->framesConsumed = 0; + pVorbis->framesRemaining = 0; + pVorbis->dataSize = 0; + + float buffer[4096]; + while (frameIndex > 0) { + mal_uint32 framesToRead = mal_countof(buffer)/pDecoder->internalChannels; + if (framesToRead > frameIndex) { + framesToRead = (mal_uint32)frameIndex; + } + + mal_uint32 framesRead = mal_vorbis_decoder_read(pVorbis, pDecoder, framesToRead, buffer); + if (framesRead == 0) { + return MAL_ERROR; + } + + frameIndex -= framesRead; + } + + return MAL_SUCCESS; +} + + +mal_result mal_decoder_internal_on_seek_to_frame__vorbis(mal_decoder* pDecoder, mal_uint64 frameIndex) +{ + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->onRead != NULL); + mal_assert(pDecoder->onSeek != NULL); + + mal_vorbis_decoder* pVorbis = (mal_vorbis_decoder*)pDecoder->pInternalDecoder; + mal_assert(pVorbis != NULL); + + return mal_vorbis_decoder_seek_to_frame(pVorbis, pDecoder, frameIndex); +} + +mal_result mal_decoder_internal_on_uninit__vorbis(mal_decoder* pDecoder) +{ + mal_vorbis_decoder* pVorbis = (mal_vorbis_decoder*)pDecoder->pInternalDecoder; + mal_assert(pVorbis != NULL); + + stb_vorbis_close(pVorbis->pInternalVorbis); + mal_free(pVorbis->pData); + mal_free(pVorbis); + + return MAL_SUCCESS; +} + +mal_uint32 mal_decoder_internal_on_read_frames__vorbis(mal_dsp* pDSP, mal_uint32 frameCount, void* pSamplesOut, void* pUserData) +{ + (void)pDSP; + + mal_decoder* pDecoder = (mal_decoder*)pUserData; + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->internalFormat == mal_format_f32); + mal_assert(pDecoder->onRead != NULL); + mal_assert(pDecoder->onSeek != NULL); + + mal_vorbis_decoder* pVorbis = (mal_vorbis_decoder*)pDecoder->pInternalDecoder; + mal_assert(pVorbis != NULL); + + return mal_vorbis_decoder_read(pVorbis, pDecoder, frameCount, pSamplesOut); +} + +mal_result mal_decoder_init_vorbis__internal(const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_assert(pConfig != NULL); + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->onRead != NULL); + mal_assert(pDecoder->onSeek != NULL); + + stb_vorbis* pInternalVorbis = NULL; + + // We grow the buffer in chunks. + size_t dataSize = 0; + size_t dataCapacity = 0; + mal_uint8* pData = NULL; + do + { + // Allocate memory for a new chunk. + dataCapacity += MAL_VORBIS_DATA_CHUNK_SIZE; + mal_uint8* pNewData = (mal_uint8*)mal_realloc(pData, dataCapacity); + if (pNewData == NULL) { + mal_free(pData); + return MAL_OUT_OF_MEMORY; + } + + pData = pNewData; + + // Fill in a chunk. + size_t bytesRead = pDecoder->onRead(pDecoder, pData + dataSize, (dataCapacity - dataSize)); + if (bytesRead == 0) { + return MAL_ERROR; + } + + dataSize += bytesRead; + if (dataSize > INT_MAX) { + return MAL_ERROR; // Too big. + } + + int vorbisError = 0; + int consumedDataSize = 0; + pInternalVorbis = stb_vorbis_open_pushdata(pData, (int)dataSize, &consumedDataSize, &vorbisError, NULL); + if (pInternalVorbis != NULL) { + // If we get here it means we were able to open the stb_vorbis decoder. There may be some leftover bytes in our buffer, so + // we need to move those bytes down to the front of the buffer since they'll be needed for future decoding. + size_t leftoverDataSize = (dataSize - (size_t)consumedDataSize); + for (size_t i = 0; i < leftoverDataSize; ++i) { + pData[i] = pData[i + consumedDataSize]; + } + + dataSize = leftoverDataSize; + break; // Success. + } else { + if (vorbisError == VORBIS_need_more_data) { + continue; + } else { + return MAL_ERROR; // Failed to open the stb_vorbis decoder. + } + } + } while (MAL_TRUE); + + + // If we get here it means we successfully opened the Vorbis decoder. + stb_vorbis_info vorbisInfo = stb_vorbis_get_info(pInternalVorbis); + + // Don't allow more than MAL_MAX_CHANNELS channels. + if (vorbisInfo.channels > MAL_MAX_CHANNELS) { + stb_vorbis_close(pInternalVorbis); + mal_free(pData); + return MAL_ERROR; // Too many channels. + } + + size_t vorbisDataSize = sizeof(mal_vorbis_decoder) + sizeof(float)*vorbisInfo.max_frame_size; + mal_vorbis_decoder* pVorbis = (mal_vorbis_decoder*)mal_malloc(vorbisDataSize); + if (pVorbis == NULL) { + stb_vorbis_close(pInternalVorbis); + mal_free(pData); + return MAL_OUT_OF_MEMORY; + } + + mal_zero_memory(pVorbis, vorbisDataSize); + pVorbis->pInternalVorbis = pInternalVorbis; + pVorbis->pData = pData; + pVorbis->dataSize = dataSize; + pVorbis->dataCapacity = dataCapacity; + + pDecoder->onSeekToFrame = mal_decoder_internal_on_seek_to_frame__vorbis; + pDecoder->onUninit = mal_decoder_internal_on_uninit__vorbis; + pDecoder->pInternalDecoder = pVorbis; + + // The internal format is always f32. + pDecoder->internalFormat = mal_format_f32; + pDecoder->internalChannels = vorbisInfo.channels; + pDecoder->internalSampleRate = vorbisInfo.sample_rate; + mal_get_standard_channel_map(mal_standard_channel_map_vorbis, pDecoder->internalChannels, pDecoder->internalChannelMap); + + mal_result result = mal_decoder__init_dsp(pDecoder, pConfig, mal_decoder_internal_on_read_frames__vorbis); + if (result != MAL_SUCCESS) { + stb_vorbis_close(pVorbis->pInternalVorbis); + mal_free(pVorbis->pData); + mal_free(pVorbis); + return result; + } + + return MAL_SUCCESS; +} +#endif + +// MP3 +#ifdef dr_mp3_h +#define MAL_HAS_MP3 + +size_t mal_decoder_internal_on_read__mp3(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + mal_decoder* pDecoder = (mal_decoder*)pUserData; + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->onRead != NULL); + + return pDecoder->onRead(pDecoder, pBufferOut, bytesToRead); +} + +drmp3_bool32 mal_decoder_internal_on_seek__mp3(void* pUserData, int offset, drmp3_seek_origin origin) +{ + mal_decoder* pDecoder = (mal_decoder*)pUserData; + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->onSeek != NULL); + + return pDecoder->onSeek(pDecoder, offset, (origin == drmp3_seek_origin_start) ? mal_seek_origin_start : mal_seek_origin_current); +} + +mal_result mal_decoder_internal_on_seek_to_frame__mp3(mal_decoder* pDecoder, mal_uint64 frameIndex) +{ + drmp3* pMP3 = (drmp3*)pDecoder->pInternalDecoder; + mal_assert(pMP3 != NULL); + + drmp3_bool32 result = drmp3_seek_to_frame(pMP3, frameIndex); + if (result) { + return MAL_SUCCESS; + } else { + return MAL_ERROR; + } +} + +mal_result mal_decoder_internal_on_uninit__mp3(mal_decoder* pDecoder) +{ + drmp3_uninit((drmp3*)pDecoder->pInternalDecoder); + mal_free(pDecoder->pInternalDecoder); + return MAL_SUCCESS; +} + +mal_uint32 mal_decoder_internal_on_read_frames__mp3(mal_dsp* pDSP, mal_uint32 frameCount, void* pSamplesOut, void* pUserData) +{ + (void)pDSP; + + mal_decoder* pDecoder = (mal_decoder*)pUserData; + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->internalFormat == mal_format_f32); + + drmp3* pMP3 = (drmp3*)pDecoder->pInternalDecoder; + mal_assert(pMP3 != NULL); + + return (mal_uint32)drmp3_read_f32(pMP3, frameCount, (float*)pSamplesOut); +} + +mal_result mal_decoder_init_mp3__internal(const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_assert(pConfig != NULL); + mal_assert(pDecoder != NULL); + + drmp3* pMP3 = (drmp3*)mal_malloc(sizeof(*pMP3)); + if (pMP3 == NULL) { + return MAL_OUT_OF_MEMORY; + } + + // Try opening the decoder first. MP3 can have variable sample rates (it's per frame/packet). We therefore need + // to use some smarts to determine the most appropriate internal sample rate. These are the rules we're going + // to use: + // + // Sample Rates + // 1) If an output sample rate is specified in pConfig we just use that. Otherwise; + // 2) Fall back to 44100. + // + // The internal channel count is always stereo, and the internal format is always f32. + drmp3_config mp3Config; + mal_zero_object(&mp3Config); + mp3Config.outputChannels = 2; + mp3Config.outputSampleRate = (pConfig->outputSampleRate != 0) ? pConfig->outputSampleRate : 44100; + if (!drmp3_init(pMP3, mal_decoder_internal_on_read__mp3, mal_decoder_internal_on_seek__mp3, pDecoder, &mp3Config)) { + return MAL_ERROR; + } + + // If we get here it means we successfully initialized the MP3 decoder. We can now initialize the rest of the mal_decoder. + pDecoder->onSeekToFrame = mal_decoder_internal_on_seek_to_frame__mp3; + pDecoder->onUninit = mal_decoder_internal_on_uninit__mp3; + pDecoder->pInternalDecoder = pMP3; + + // Internal format. + pDecoder->internalFormat = mal_format_f32; + pDecoder->internalChannels = pMP3->channels; + pDecoder->internalSampleRate = pMP3->sampleRate; + mal_get_standard_channel_map(mal_standard_channel_map_default, pDecoder->internalChannels, pDecoder->internalChannelMap); + + mal_result result = mal_decoder__init_dsp(pDecoder, pConfig, mal_decoder_internal_on_read_frames__mp3); + if (result != MAL_SUCCESS) { + mal_free(pMP3); + return result; + } + + return MAL_SUCCESS; +} +#endif + +mal_result mal_decoder__preinit(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_assert(pConfig != NULL); + + if (pDecoder == NULL) { + return MAL_INVALID_ARGS; + } + + mal_zero_object(pDecoder); + + if (onRead == NULL || onSeek == NULL) { + return MAL_INVALID_ARGS; + } + + pDecoder->onRead = onRead; + pDecoder->onSeek = onSeek; + pDecoder->pUserData = pUserData; + + (void)pConfig; + return MAL_SUCCESS; +} + +mal_result mal_decoder_init_wav(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_decoder_config config = mal_decoder_config_init_copy(pConfig); + + mal_result result = mal_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + if (result != MAL_SUCCESS) { + return result; + } + +#ifdef MAL_HAS_WAV + return mal_decoder_init_wav__internal(&config, pDecoder); +#else + return MAL_NO_BACKEND; +#endif +} + +mal_result mal_decoder_init_flac(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_decoder_config config = mal_decoder_config_init_copy(pConfig); + + mal_result result = mal_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + if (result != MAL_SUCCESS) { + return result; + } + +#ifdef MAL_HAS_FLAC + return mal_decoder_init_flac__internal(&config, pDecoder); +#else + return MAL_NO_BACKEND; +#endif +} + +mal_result mal_decoder_init_vorbis(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_decoder_config config = mal_decoder_config_init_copy(pConfig); + + mal_result result = mal_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + if (result != MAL_SUCCESS) { + return result; + } + +#ifdef MAL_HAS_VORBIS + return mal_decoder_init_vorbis__internal(&config, pDecoder); +#else + return MAL_NO_BACKEND; +#endif +} + +mal_result mal_decoder_init_mp3(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_decoder_config config = mal_decoder_config_init_copy(pConfig); + + mal_result result = mal_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + if (result != MAL_SUCCESS) { + return result; + } + +#ifdef MAL_HAS_MP3 + return mal_decoder_init_mp3__internal(&config, pDecoder); +#else + return MAL_NO_BACKEND; +#endif +} + +mal_result mal_decoder_init(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_decoder_config config = mal_decoder_config_init_copy(pConfig); + + mal_result result = mal_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + if (result != MAL_SUCCESS) { + return result; + } + + // We use trial and error to open a decoder. + result = MAL_NO_BACKEND; + +#ifdef MAL_HAS_WAV + if (result != MAL_SUCCESS) { + result = mal_decoder_init_wav__internal(&config, pDecoder); + if (result != MAL_SUCCESS) { + onSeek(pDecoder, 0, mal_seek_origin_start); + } + } +#endif +#ifdef MAL_HAS_FLAC + if (result != MAL_SUCCESS) { + result = mal_decoder_init_flac__internal(&config, pDecoder); + if (result != MAL_SUCCESS) { + onSeek(pDecoder, 0, mal_seek_origin_start); + } + } +#endif +#ifdef MAL_HAS_MP3 + if (result != MAL_SUCCESS) { + result = mal_decoder_init_mp3__internal(&config, pDecoder); + if (result != MAL_SUCCESS) { + onSeek(pDecoder, 0, mal_seek_origin_start); + } + } +#endif +#ifdef MAL_HAS_VORBIS + if (result != MAL_SUCCESS) { + result = mal_decoder_init_vorbis__internal(&config, pDecoder); + if (result != MAL_SUCCESS) { + onSeek(pDecoder, 0, mal_seek_origin_start); + } + } +#endif + + if (result != MAL_SUCCESS) { + return result; + } + + return result; +} + + +size_t mal_decoder__on_read_memory(mal_decoder* pDecoder, void* pBufferOut, size_t bytesToRead) +{ + mal_assert(pDecoder->memory.dataSize >= pDecoder->memory.currentReadPos); + + size_t bytesRemaining = pDecoder->memory.dataSize - pDecoder->memory.currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + mal_copy_memory(pBufferOut, pDecoder->memory.pData + pDecoder->memory.currentReadPos, bytesToRead); + pDecoder->memory.currentReadPos += bytesToRead; + } + + return bytesToRead; +} + +mal_bool32 mal_decoder__on_seek_memory(mal_decoder* pDecoder, int byteOffset, mal_seek_origin origin) +{ + if (origin == mal_seek_origin_current) { + if (byteOffset > 0) { + if (pDecoder->memory.currentReadPos + byteOffset > pDecoder->memory.dataSize) { + byteOffset = (int)(pDecoder->memory.dataSize - pDecoder->memory.currentReadPos); // Trying to seek too far forward. + } + } else { + if (pDecoder->memory.currentReadPos < (size_t)-byteOffset) { + byteOffset = -(int)pDecoder->memory.currentReadPos; // Trying to seek too far backwards. + } + } + + // This will never underflow thanks to the clamps above. + pDecoder->memory.currentReadPos += byteOffset; + } else { + if ((mal_uint32)byteOffset <= pDecoder->memory.dataSize) { + pDecoder->memory.currentReadPos = byteOffset; + } else { + pDecoder->memory.currentReadPos = pDecoder->memory.dataSize; // Trying to seek too far forward. + } + } + + return MAL_TRUE; +} + +mal_result mal_decoder__preinit_memory(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + if (pDecoder == NULL) { + return MAL_INVALID_ARGS; + } + + mal_zero_object(pDecoder); + + if (pData == NULL || dataSize == 0) { + return MAL_INVALID_ARGS; + } + + pDecoder->memory.pData = (const mal_uint8*)pData; + pDecoder->memory.dataSize = dataSize; + pDecoder->memory.currentReadPos = 0; + + (void)pConfig; + return MAL_SUCCESS; +} + +mal_result mal_decoder_init_memory(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_result result = mal_decoder__preinit_memory(pData, dataSize, pConfig, pDecoder); + if (result != MAL_SUCCESS) { + return result; + } + + return mal_decoder_init(mal_decoder__on_read_memory, mal_decoder__on_seek_memory, NULL, pConfig, pDecoder); +} + +mal_result mal_decoder_init_memory_wav(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_result result = mal_decoder__preinit_memory(pData, dataSize, pConfig, pDecoder); + if (result != MAL_SUCCESS) { + return result; + } + + return mal_decoder_init_wav(mal_decoder__on_read_memory, mal_decoder__on_seek_memory, NULL, pConfig, pDecoder); +} + +mal_result mal_decoder_init_memory_flac(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_result result = mal_decoder__preinit_memory(pData, dataSize, pConfig, pDecoder); + if (result != MAL_SUCCESS) { + return result; + } + + return mal_decoder_init_flac(mal_decoder__on_read_memory, mal_decoder__on_seek_memory, NULL, pConfig, pDecoder); +} + +mal_result mal_decoder_init_memory_vorbis(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_result result = mal_decoder__preinit_memory(pData, dataSize, pConfig, pDecoder); + if (result != MAL_SUCCESS) { + return result; + } + + return mal_decoder_init_vorbis(mal_decoder__on_read_memory, mal_decoder__on_seek_memory, NULL, pConfig, pDecoder); +} + +mal_result mal_decoder_init_memory_mp3(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_result result = mal_decoder__preinit_memory(pData, dataSize, pConfig, pDecoder); + if (result != MAL_SUCCESS) { + return result; + } + + return mal_decoder_init_mp3(mal_decoder__on_read_memory, mal_decoder__on_seek_memory, NULL, pConfig, pDecoder); +} + +#ifndef MAL_NO_STDIO +#include +#ifndef _MSC_VER +#include // For strcasecmp(). +#endif + +const char* mal_path_file_name(const char* path) +{ + if (path == NULL) { + return NULL; + } + + const char* fileName = path; + + // We just loop through the path until we find the last slash. + while (path[0] != '\0') { + if (path[0] == '/' || path[0] == '\\') { + fileName = path; + } + + path += 1; + } + + // At this point the file name is sitting on a slash, so just move forward. + while (fileName[0] != '\0' && (fileName[0] == '/' || fileName[0] == '\\')) { + fileName += 1; + } + + return fileName; +} + +const char* mal_path_extension(const char* path) +{ + if (path == NULL) { + path = ""; + } + + const char* extension = mal_path_file_name(path); + const char* lastOccurance = NULL; + + // Just find the last '.' and return. + while (extension[0] != '\0') { + if (extension[0] == '.') { + extension += 1; + lastOccurance = extension; + } + + extension += 1; + } + + return (lastOccurance != NULL) ? lastOccurance : extension; +} + +mal_bool32 mal_path_extension_equal(const char* path, const char* extension) +{ + if (path == NULL || extension == NULL) { + return MAL_FALSE; + } + + const char* ext1 = extension; + const char* ext2 = mal_path_extension(path); + +#ifdef _MSC_VER + return _stricmp(ext1, ext2) == 0; +#else + return strcasecmp(ext1, ext2) == 0; +#endif +} + +size_t mal_decoder__on_read_stdio(mal_decoder* pDecoder, void* pBufferOut, size_t bytesToRead) +{ + return fread(pBufferOut, 1, bytesToRead, (FILE*)pDecoder->pUserData); +} + +mal_bool32 mal_decoder__on_seek_stdio(mal_decoder* pDecoder, int byteOffset, mal_seek_origin origin) +{ + return fseek((FILE*)pDecoder->pUserData, byteOffset, (origin == mal_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; +} + +mal_result mal_decoder__preinit_file(const char* pFilePath, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + if (pDecoder == NULL) { + return MAL_INVALID_ARGS; + } + + mal_zero_object(pDecoder); + + if (pFilePath == NULL || pFilePath[0] == '\0') { + return MAL_INVALID_ARGS; + } + + FILE* pFile; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (fopen_s(&pFile, pFilePath, "rb") != 0) { + return MAL_ERROR; + } +#else + pFile = fopen(pFilePath, "rb"); + if (pFile == NULL) { + return MAL_ERROR; + } +#endif + + // We need to manually set the user data so the calls to mal_decoder__on_seek_stdio() succeed. + pDecoder->pUserData = pFile; + + (void)pConfig; + return MAL_SUCCESS; +} + +mal_result mal_decoder_init_file(const char* pFilePath, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_result result = mal_decoder__preinit_file(pFilePath, pConfig, pDecoder); // This sets pDecoder->pUserData to a FILE*. + if (result != MAL_SUCCESS) { + return result; + } + + // WAV + if (mal_path_extension_equal(pFilePath, "wav")) { + result = mal_decoder_init_wav(mal_decoder__on_read_stdio, mal_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); + if (result == MAL_SUCCESS) { + return MAL_SUCCESS; + } + + mal_decoder__on_seek_stdio(pDecoder, 0, mal_seek_origin_start); + } + + // FLAC + if (mal_path_extension_equal(pFilePath, "flac")) { + result = mal_decoder_init_flac(mal_decoder__on_read_stdio, mal_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); + if (result == MAL_SUCCESS) { + return MAL_SUCCESS; + } + + mal_decoder__on_seek_stdio(pDecoder, 0, mal_seek_origin_start); + } + + // MP3 + if (mal_path_extension_equal(pFilePath, "mp3")) { + result = mal_decoder_init_mp3(mal_decoder__on_read_stdio, mal_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); + if (result == MAL_SUCCESS) { + return MAL_SUCCESS; + } + + mal_decoder__on_seek_stdio(pDecoder, 0, mal_seek_origin_start); + } + + // Trial and error. + return mal_decoder_init(mal_decoder__on_read_stdio, mal_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); +} + +mal_result mal_decoder_init_file_wav(const char* pFilePath, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_result result = mal_decoder__preinit_file(pFilePath, pConfig, pDecoder); + if (result != MAL_SUCCESS) { + return result; + } + + return mal_decoder_init_wav(mal_decoder__on_read_stdio, mal_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); +} + +mal_result mal_decoder_init_file_flac(const char* pFilePath, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_result result = mal_decoder__preinit_file(pFilePath, pConfig, pDecoder); + if (result != MAL_SUCCESS) { + return result; + } + + return mal_decoder_init_flac(mal_decoder__on_read_stdio, mal_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); +} + +mal_result mal_decoder_init_file_vorbis(const char* pFilePath, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_result result = mal_decoder__preinit_file(pFilePath, pConfig, pDecoder); + if (result != MAL_SUCCESS) { + return result; + } + + return mal_decoder_init_vorbis(mal_decoder__on_read_stdio, mal_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); +} + +mal_result mal_decoder_init_file_mp3(const char* pFilePath, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_result result = mal_decoder__preinit_file(pFilePath, pConfig, pDecoder); + if (result != MAL_SUCCESS) { + return result; + } + + return mal_decoder_init_mp3(mal_decoder__on_read_stdio, mal_decoder__on_seek_stdio, pDecoder->pUserData, pConfig, pDecoder); +} +#endif + +mal_result mal_decoder_uninit(mal_decoder* pDecoder) +{ + if (pDecoder == NULL) { + return MAL_INVALID_ARGS; + } + + if (pDecoder->onUninit) { + pDecoder->onUninit(pDecoder); + } + +#ifndef MAL_NO_STDIO + // If we have a file handle, close it. + if (pDecoder->onRead == mal_decoder__on_read_stdio) { + fclose((FILE*)pDecoder->pUserData); + } +#endif + + return MAL_SUCCESS; +} + +mal_uint64 mal_decoder_read(mal_decoder* pDecoder, mal_uint64 frameCount, void* pFramesOut) +{ + if (pDecoder == NULL) return 0; + + return mal_dsp_read_ex(&pDecoder->dsp, frameCount, pFramesOut, MAL_TRUE, pDecoder->dsp.pUserData); +} + +mal_result mal_decoder_seek_to_frame(mal_decoder* pDecoder, mal_uint64 frameIndex) +{ + if (pDecoder == NULL) return 0; + + if (pDecoder->onSeekToFrame) { + return pDecoder->onSeekToFrame(pDecoder, frameIndex); + } + + // Should never get here, but if we do it means onSeekToFrame was not set by the backend. + return MAL_INVALID_ARGS; +} +#endif // MAL_NO_DECODING + + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// GENERATION +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +mal_result mal_sine_wave_init(double amplitude, double periodsPerSecond, mal_uint32 sampleRate, mal_sine_wave* pSineWave) +{ + if (pSineWave == NULL) { + return MAL_INVALID_ARGS; + } + mal_zero_object(pSineWave); + + if (amplitude == 0 || periodsPerSecond == 0) { + return MAL_INVALID_ARGS; + } + + if (amplitude > 1) { + amplitude = 1; + } + if (amplitude < -1) { + amplitude = -1; + } + + pSineWave->amplitude = amplitude; + pSineWave->periodsPerSecond = periodsPerSecond; + pSineWave->delta = MAL_PI_D*2 / sampleRate; + pSineWave->time = 0; + + return MAL_SUCCESS; +} + +mal_uint64 mal_sine_wave_read(mal_sine_wave* pSineWave, mal_uint64 count, float* pSamples) +{ + if (pSineWave == NULL) { + return 0; + } + + if (pSamples != NULL) { + for (mal_uint64 i = 0; i < count; i += 1) { + pSamples[i] = (float)(sin(pSineWave->time * pSineWave->periodsPerSecond) * pSineWave->amplitude); + pSineWave->time += pSineWave->delta; + } + } else { + pSineWave->time += pSineWave->delta * count; + } + + return count; +} + + +#endif // MAL_IMPLEMENTATION + // REVISION HISTORY // ================ // +// v0.x - 2018-xx-xx +// - API CHANGE: Replace device enumeration APIs. mal_enumerate_devices() has been replaced with +// mal_context_get_devices(). An additional low-level device enumration API has been introduced called +// mal_context_enumerate_devices() which uses a callback to report devices. +// - API CHANGE: Rename mal_get_sample_size_in_bytes() to mal_get_bytes_per_sample() and add +// mal_get_bytes_per_frame(). +// - API CHANGE: Replace mal_device_config.preferExclusiveMode with mal_device_config.shareMode. +// - This new config can be set to mal_share_mode_shared (default) or mal_share_mode_exclusive. +// - API CHANGE: Remove excludeNullDevice from mal_context_config.alsa. +// - API CHANGE: Rename MAL_MAX_SAMPLE_SIZE_IN_BYTES to MAL_MAX_PCM_SAMPLE_SIZE_IN_BYTES. +// - API CHANGE: Change the default channel mapping to the standard Microsoft mapping. +// - API CHANGE: Remove backend-specific result codes. +// - API CHANGE: Changes to the format conversion APIs (mal_pcm_f32_to_s16(), etc.) +// - Add support for PulseAudio. +// - This is the highest priority backend on Linux (higher priority than ALSA) since it is commonly +// installed by default on many of the popular distros and offer's more seamless integration on +// platforms where PulseAudio is used. In addition, if PulseAudio is installed and running (which +// is extremely common), it's better to just use PulseAudio directly rather than going through the +// "pulse" ALSA plugin (which is what the "default" ALSA device is likely set to). +// - Add support for JACK. +// - Remove dependency on asound.h for the ALSA backend. This means the ALSA development packages are no +// longer required to build mini_al. +// - Remove dependency on dsound.h for the DirectSound backend. This fixes build issues with some +// distributions of MinGW. +// - Remove dependency on audioclient.h for the WASAPI backend. This fixes build issues with some +// distributions of MinGW. +// - Add support for configuring the priority of the worker thread. +// - Add a sine wave generator. +// - Improve efficiency of sample rate conversion. +// - Introduce the notion of standard channel maps. Use mal_get_standard_channel_map(). +// - Introduce the notion of default device configurations. A default config uses the same configuration +// as the backend's internal device, and as such results in a pass-through data transmission pipeline. +// - Add support for passing in NULL for the device config in mal_device_init(), which uses a default +// config. This requires manually calling mal_device_set_send/recv_callback(). +// - Make mal_device_init_ex() more robust. +// - Make some APIs more const-correct. +// - Fix errors with OpenAL detection. +// - Fix some memory leaks. +// - Miscellaneous bug fixes. +// - Documentation updates. +// +// v0.7 - 2018-02-25 +// - API CHANGE: Change mal_src_read_frames() and mal_dsp_read_frames() to use 64-bit sample counts. +// - Add decoder APIs for loading WAV, FLAC, Vorbis and MP3 files. +// - Allow opening of devices without a context. +// - In this case the context is created and managed internally by the device. +// - Change the default channel mapping to the same as that used by FLAC. +// - Fix build errors with macOS. +// +// v0.6c - 2018-02-12 +// - Fix build errors with BSD/OSS. +// // v0.6b - 2018-02-03 // - Fix some warnings when compiling with Visual C++. //