Commit 560013b8 authored by David Reid's avatar David Reid

Prepare for update to new APIs of dr_* decoders.

parent ed1bcd1a
This source diff could not be displayed because it is too large. You can view the blob instead.
// MP3 audio decoder. Public domain. See "unlicense" statement at the end of this file.
// dr_mp3 - v0.3.2 - 2018-09-11
// dr_mp3 - v0.4.0 - 2018-xx-xx
//
// David Reid - mackron@gmail.com
//
......@@ -22,7 +22,7 @@
//
// ...
//
// drmp3_uint64 framesRead = drmp3_read_f32(pMP3, framesToRead, pFrames);
// drmp3_uint64 framesRead = drmp3_read_pcm_frames_f32(pMP3, framesToRead, pFrames);
//
// The drmp3 object is transparent so you can get access to the channel count and sample rate like so:
//
......@@ -35,12 +35,12 @@
// The example above initializes a decoder from a file, but you can also initialize it from a block of memory and read and seek
// callbacks with drmp3_init_memory() and drmp3_init() respectively.
//
// You do need to do any annoying memory management when reading PCM frames - this is all managed internally. You can request
// any number of PCM frames in each call to drmp3_read_f32() and it will return as many PCM frames as it can, up to the requested
// amount.
// You do not need to do any annoying memory management when reading PCM frames - this is all managed internally. You can request
// any number of PCM frames in each call to drmp3_read_pcm_frames_f32() and it will return as many PCM frames as it can, up to the
// requested amount.
//
// You can also decode an entire file in one go with drmp3_open_and_decode_f32(), drmp3_open_and_decode_memory_f32() and
// drmp3_open_and_decode_file_f32().
// You can also decode an entire file in one go with drmp3_open_and_read_f32(), drmp3_open_memory_and_read_f32() and
// drmp3_open_file_and_read_f32().
//
//
// OPTIONS
......@@ -52,11 +52,6 @@
//
// #define DR_MP3_NO_SIMD
// Disable SIMD optimizations.
//
//
// LIMITATIONS
// ===========
// - Seeking is extremely inefficient.
#ifndef dr_mp3_h
#define dr_mp3_h
......@@ -92,7 +87,8 @@ typedef drmp3_uint32 drmp3_bool32;
#define DRMP3_TRUE 1
#define DRMP3_FALSE 0
#define DRMP3_MAX_SAMPLES_PER_FRAME (1152*2)
#define DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME 1152
#define DRMP3_MAX_SAMPLES_PER_FRAME (DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME*2)
// Low Level Push API
......@@ -162,7 +158,7 @@ struct drmp3_src
{
struct
{
float alpha;
double alpha;
drmp3_bool32 isPrevFramesLoaded : 1;
drmp3_bool32 isNextFramesLoaded : 1;
} linear;
......@@ -175,6 +171,14 @@ typedef enum
drmp3_seek_origin_current
} drmp3_seek_origin;
typedef struct
{
drmp3_uint64 seekPosInBytes; // Points to the first byte of an MP3 frame.
drmp3_uint64 pcmFrameIndex; // The index of the PCM frame this seek point targets.
drmp3_uint16 mp3FramesToDiscard; // The number of whole MP3 frames to be discarded before pcmFramesToDiscard.
drmp3_uint16 pcmFramesToDiscard; // The number of leading samples to read and discard. These are discarded after mp3FramesToDiscard.
} drmp3_seek_point;
// Callback for when data is read. Return value is the number of bytes actually read.
//
// pUserData [in] The user data that was passed to drmp3_init(), drmp3_open() and family.
......@@ -214,12 +218,16 @@ typedef struct
drmp3_read_proc onRead;
drmp3_seek_proc onSeek;
void* pUserData;
drmp3_uint32 frameChannels; // The number of channels in the currently loaded MP3 frame. Internal use only.
drmp3_uint32 frameSampleRate; // The sample rate of the currently loaded MP3 frame. Internal use only.
drmp3_uint32 framesConsumed;
drmp3_uint32 framesRemaining;
drmp3_uint8 frames[sizeof(float)*DRMP3_MAX_SAMPLES_PER_FRAME]; // <-- Multipled by sizeof(float) to ensure there's enough room for DR_MP3_FLOAT_OUTPUT.
drmp3_uint32 mp3FrameChannels; // The number of channels in the currently loaded MP3 frame. Internal use only.
drmp3_uint32 mp3FrameSampleRate; // The sample rate of the currently loaded MP3 frame. Internal use only.
drmp3_uint32 pcmFramesConsumedInMP3Frame;
drmp3_uint32 pcmFramesRemainingInMP3Frame;
drmp3_uint8 pcmFrames[sizeof(float)*DRMP3_MAX_SAMPLES_PER_FRAME]; // <-- Multipled by sizeof(float) to ensure there's enough room for DR_MP3_FLOAT_OUTPUT.
drmp3_uint64 currentPCMFrame; // The current PCM frame, globally, based on the output sample rate. Mainly used for seeking.
drmp3_uint64 streamCursor; // The current byte the decoder is sitting on in the raw stream.
drmp3_src src;
drmp3_seek_point* pSeekPoints; // NULL by default. Set with drmp3_bind_seek_table(). Memory is owned by the client. dr_mp3 will never attempt to free this pointer.
drmp3_uint32 seekPointCount; // The number of items in pSeekPoints. When set to 0 assumes to no seek table. Defaults to zero.
size_t dataSize;
size_t dataCapacity;
drmp3_uint8* pData;
......@@ -268,12 +276,38 @@ void drmp3_uninit(drmp3* pMP3);
// Reads PCM frames as interleaved 32-bit IEEE floating point PCM.
//
// Note that framesToRead specifies the number of PCM frames to read, _not_ the number of MP3 frames.
drmp3_uint64 drmp3_read_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut);
drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut);
// Seeks to a specific frame.
//
// Note that this is _not_ an MP3 frame, but rather a PCM frame.
drmp3_bool32 drmp3_seek_to_frame(drmp3* pMP3, drmp3_uint64 frameIndex);
drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex);
// Calculates the total number of PCM frames in the MP3 stream. Cannot be used for infinite streams such as internet
// radio. Runs in linear time. Returns 0 on error.
drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3);
// Calculates the total number of MP3 frames in the MP3 stream. Cannot be used for infinite streams such as internet
// radio. Runs in linear time. Returns 0 on error.
drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3);
// Calculates the seekpoints based on PCM frames. This is slow.
//
// pSeekpoint count is a pointer to a uint32 containing the seekpoint count. On input it contains the desired count.
// On output it contains the actual count. The reason for this design is that the client may request too many
// seekpoints, in which case dr_mp3 will return a corrected count.
//
// Note that seektable seeking is not quite sample exact when the MP3 stream contains inconsistent sample rates.
drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints);
// Binds a seek table to the decoder.
//
// This does _not_ make a copy of pSeekPoints - it only references it. It is up to the application to ensure this
// remains valid while it is bound to the decoder.
//
// Use drmp3_calculate_seek_points() to calculate the seek points.
drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints);
// Opens an decodes an entire MP3 stream as a single operation.
......@@ -281,10 +315,10 @@ drmp3_bool32 drmp3_seek_to_frame(drmp3* pMP3, drmp3_uint64 frameIndex);
// pConfig is both an input and output. On input it contains what you want. On output it contains what you got.
//
// Free the returned pointer with drmp3_free().
float* drmp3_open_and_decode_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount);
float* drmp3_open_and_decode_memory_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount);
float* drmp3_open_and_read_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount);
float* drmp3_open_memory_and_read_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount);
#ifndef DR_MP3_NO_STDIO
float* drmp3_open_and_decode_file_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount);
float* drmp3_open_file_and_read_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount);
#endif
// Frees any memory that was allocated by a public drmp3 API.
......@@ -1958,11 +1992,6 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes
info->layer = 4 - DRMP3_HDR_GET_LAYER(hdr);
info->bitrate_kbps = drmp3_hdr_bitrate_kbps(hdr);
if (!pcm)
{
return drmp3_hdr_frame_samples(hdr);
}
drmp3_bs_init(bs_frame, hdr + DRMP3_HDR_SIZE, frame_size - DRMP3_HDR_SIZE);
if (DRMP3_HDR_IS_CRC(hdr))
{
......@@ -1978,7 +2007,7 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes
return 0;
}
success = drmp3_L3_restore_reservoir(dec, bs_frame, &scratch, main_data_begin);
if (success)
if (success && pcm != NULL)
{
for (igr = 0; igr < (DRMP3_HDR_TEST_MPEG1(hdr) ? 2 : 1); igr++, pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*576*info->channels))
{
......@@ -1993,6 +2022,10 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes
#ifdef DR_MP3_ONLY_MP3
return 0;
#else
if (pcm == NULL) {
return drmp3_hdr_frame_samples(hdr);
}
drmp3_L12_scale_info sci[1];
drmp3_L12_read_scale_info(hdr, bs_frame, sci);
......@@ -2015,6 +2048,7 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes
}
#endif
}
return success*drmp3_hdr_frame_samples(dec->header);
}
......@@ -2097,10 +2131,13 @@ void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples)
// Options.
#ifndef DR_MP3_DEFAULT_CHANNELS
#define DR_MP3_DEFAULT_CHANNELS 2
#define DR_MP3_DEFAULT_CHANNELS 2
#endif
#ifndef DR_MP3_DEFAULT_SAMPLE_RATE
#define DR_MP3_DEFAULT_SAMPLE_RATE 44100
#define DR_MP3_DEFAULT_SAMPLE_RATE 44100
#endif
#ifndef DRMP3_SEEK_LEADING_MP3_FRAMES
#define DRMP3_SEEK_LEADING_MP3_FRAMES 2
#endif
......@@ -2323,7 +2360,7 @@ drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCou
pSRC->algo.linear.isNextFramesLoaded = DRMP3_TRUE;
}
float factor = (float)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut;
double factor = (double)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut;
drmp3_uint64 totalFramesRead = 0;
while (frameCount > 0) {
......@@ -2331,7 +2368,7 @@ drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCou
float* pPrevFrame = pSRC->bin;
float* pNextFrame = pSRC->bin + pSRC->config.channels;
drmp3_blend_f32((float*)pFramesOut, pPrevFrame, pNextFrame, pSRC->algo.linear.alpha, pSRC->config.channels);
drmp3_blend_f32((float*)pFramesOut, pPrevFrame, pNextFrame, (float)pSRC->algo.linear.alpha, pSRC->config.channels);
pSRC->algo.linear.alpha += factor;
......@@ -2376,35 +2413,91 @@ drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCou
}
static size_t drmp3__on_read(drmp3* pMP3, void* pBufferOut, size_t bytesToRead)
{
size_t bytesRead = pMP3->onRead(pMP3->pUserData, pBufferOut, bytesToRead);
pMP3->streamCursor += bytesRead;
return bytesRead;
}
static drmp3_bool32 drmp3__on_seek(drmp3* pMP3, int offset, drmp3_seek_origin origin)
{
drmp3_assert(offset >= 0);
if (!pMP3->onSeek(pMP3->pUserData, offset, origin)) {
return DRMP3_FALSE;
}
if (origin == drmp3_seek_origin_start) {
pMP3->streamCursor = (drmp3_uint64)offset;
} else {
pMP3->streamCursor += offset;
}
return DRMP3_TRUE;
}
static drmp3_bool32 drmp3__on_seek_64(drmp3* pMP3, drmp3_uint64 offset, drmp3_seek_origin origin)
{
if (offset <= 0x7FFFFFFF) {
return drmp3__on_seek(pMP3, (int)offset, origin);
}
// Getting here "offset" is too large for a 32-bit integer. We just keep seeking forward until we hit the offset.
if (!drmp3__on_seek(pMP3, 0x7FFFFFFF, drmp3_seek_origin_start)) {
return DRMP3_FALSE;
}
offset -= 0x7FFFFFFF;
while (offset > 0) {
if (offset <= 0x7FFFFFFF) {
if (!drmp3__on_seek(pMP3, (int)offset, drmp3_seek_origin_current)) {
return DRMP3_FALSE;
}
offset = 0;
} else {
if (!drmp3__on_seek(pMP3, 0x7FFFFFFF, drmp3_seek_origin_current)) {
return DRMP3_FALSE;
}
offset -= 0x7FFFFFFF;
}
}
return DRMP3_TRUE;
}
static drmp3_bool32 drmp3_decode_next_frame(drmp3* pMP3)
static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3_bool32 discard)
{
drmp3_assert(pMP3 != NULL);
drmp3_assert(pMP3->onRead != NULL);
if (pMP3->atEnd) {
return DRMP3_FALSE;
return 0;
}
do
{
drmp3_uint32 pcmFramesRead = 0;
do {
// minimp3 recommends doing data submission in 16K chunks. If we don't have at least 16K bytes available, get more.
if (pMP3->dataSize < DRMP3_DATA_CHUNK_SIZE) {
if (pMP3->dataCapacity < DRMP3_DATA_CHUNK_SIZE) {
pMP3->dataCapacity = DRMP3_DATA_CHUNK_SIZE;
drmp3_uint8* pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity);
if (pNewData == NULL) {
return DRMP3_FALSE; // Out of memory.
return 0; // Out of memory.
}
pMP3->pData = pNewData;
}
size_t bytesRead = pMP3->onRead(pMP3->pUserData, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize));
size_t bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize));
if (bytesRead == 0) {
if (pMP3->dataSize == 0) {
pMP3->atEnd = DRMP3_TRUE;
return DRMP3_FALSE; // No data.
return 0; // No data.
}
}
......@@ -2413,51 +2506,81 @@ static drmp3_bool32 drmp3_decode_next_frame(drmp3* pMP3)
if (pMP3->dataSize > INT_MAX) {
pMP3->atEnd = DRMP3_TRUE;
return DRMP3_FALSE; // File too big.
return 0; // File too big.
}
drmp3dec_frame_info info;
drmp3_uint32 samplesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData, (int)pMP3->dataSize, (drmp3d_sample_t*)pMP3->frames, &info); // <-- Safe size_t -> int conversion thanks to the check above.
if (samplesRead != 0) {
size_t leftoverDataSize = (pMP3->dataSize - (size_t)info.frame_bytes);
for (size_t i = 0; i < leftoverDataSize; ++i) {
pMP3->pData[i] = pMP3->pData[i + (size_t)info.frame_bytes];
}
pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData, (int)pMP3->dataSize, pPCMFrames, &info); // <-- Safe size_t -> int conversion thanks to the check above.
// Consume the data.
size_t leftoverDataSize = (pMP3->dataSize - (size_t)info.frame_bytes);
if (info.frame_bytes > 0) {
memmove(pMP3->pData, pMP3->pData + info.frame_bytes, leftoverDataSize);
pMP3->dataSize = leftoverDataSize;
pMP3->framesConsumed = 0;
pMP3->framesRemaining = samplesRead;
pMP3->frameChannels = info.channels;
pMP3->frameSampleRate = info.hz;
drmp3_src_set_input_sample_rate(&pMP3->src, pMP3->frameSampleRate);
}
// pcmFramesRead will be equal to 0 if decoding failed. If it is zero and info.frame_bytes > 0 then we have successfully
// decoded the frame. A special case is if we are wanting to discard the frame, in which case we return successfully.
if (pcmFramesRead > 0 || (info.frame_bytes > 0 && discard)) {
pcmFramesRead = drmp3_hdr_frame_samples(pMP3->decoder.header);
pMP3->pcmFramesConsumedInMP3Frame = 0;
pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead;
pMP3->mp3FrameChannels = info.channels;
pMP3->mp3FrameSampleRate = info.hz;
drmp3_src_set_input_sample_rate(&pMP3->src, pMP3->mp3FrameSampleRate);
break;
} else {
} else if (info.frame_bytes == 0) {
// Need more data. minimp3 recommends doing data submission in 16K chunks.
if (pMP3->dataCapacity == pMP3->dataSize) {
// No room. Expand.
pMP3->dataCapacity += DRMP3_DATA_CHUNK_SIZE;
drmp3_uint8* pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity);
if (pNewData == NULL) {
return DRMP3_FALSE; // Out of memory.
return 0; // Out of memory.
}
pMP3->pData = pNewData;
}
// Fill in a chunk.
size_t bytesRead = pMP3->onRead(pMP3->pUserData, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize));
size_t bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize));
if (bytesRead == 0) {
pMP3->atEnd = DRMP3_TRUE;
return DRMP3_FALSE; // Error reading more data.
return 0; // Error reading more data.
}
pMP3->dataSize += bytesRead;
}
} while (DRMP3_TRUE);
return DRMP3_TRUE;
return pcmFramesRead;
}
static drmp3_uint32 drmp3_decode_next_frame(drmp3* pMP3)
{
drmp3_assert(pMP3 != NULL);
return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames, DRMP3_FALSE);
}
#if 0
static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3)
{
drmp3_assert(pMP3 != NULL);
drmp3_uint32 pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, NULL);
if (pcmFrameCount == 0) {
return 0;
}
// We have essentially just skipped past the frame, so just set the remaining samples to 0.
pMP3->currentPCMFrame += pcmFrameCount;
pMP3->pcmFramesConsumedInMP3Frame = pcmFrameCount;
pMP3->pcmFramesRemainingInMP3Frame = 0;
return pcmFrameCount;
}
#endif
static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, void* pUserData)
{
drmp3* pMP3 = (drmp3*)pUserData;
......@@ -2465,64 +2588,64 @@ static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, voi
drmp3_assert(pMP3->onRead != NULL);
float* pFramesOutF = (float*)pFramesOut;
drmp3_uint32 totalFramesRead = 0;
drmp3_uint64 totalFramesRead = 0;
while (frameCount > 0) {
// Read from the in-memory buffer first.
while (pMP3->framesRemaining > 0 && frameCount > 0) {
drmp3d_sample_t* frames = (drmp3d_sample_t*)pMP3->frames;
while (pMP3->pcmFramesRemainingInMP3Frame > 0 && frameCount > 0) {
drmp3d_sample_t* frames = (drmp3d_sample_t*)pMP3->pcmFrames;
#ifndef DR_MP3_FLOAT_OUTPUT
if (pMP3->frameChannels == 1) {
if (pMP3->mp3FrameChannels == 1) {
if (pMP3->channels == 1) {
// Mono -> Mono.
pFramesOutF[0] = frames[pMP3->framesConsumed] / 32768.0f;
pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f;
} else {
// Mono -> Stereo.
pFramesOutF[0] = frames[pMP3->framesConsumed] / 32768.0f;
pFramesOutF[1] = frames[pMP3->framesConsumed] / 32768.0f;
pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f;
pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f;
}
} else {
if (pMP3->channels == 1) {
// Stereo -> Mono
float sample = 0;
sample += frames[(pMP3->framesConsumed*pMP3->frameChannels)+0] / 32768.0f;
sample += frames[(pMP3->framesConsumed*pMP3->frameChannels)+1] / 32768.0f;
sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f;
sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f;
pFramesOutF[0] = sample * 0.5f;
} else {
// Stereo -> Stereo
pFramesOutF[0] = frames[(pMP3->framesConsumed*pMP3->frameChannels)+0] / 32768.0f;
pFramesOutF[1] = frames[(pMP3->framesConsumed*pMP3->frameChannels)+1] / 32768.0f;
pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f;
pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f;
}
}
#else
if (pMP3->frameChannels == 1) {
if (pMP3->mp3FrameChannels == 1) {
if (pMP3->channels == 1) {
// Mono -> Mono.
pFramesOutF[0] = frames[pMP3->framesConsumed];
pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame];
} else {
// Mono -> Stereo.
pFramesOutF[0] = frames[pMP3->framesConsumed];
pFramesOutF[1] = frames[pMP3->framesConsumed];
pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame];
pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame];
}
} else {
if (pMP3->channels == 1) {
// Stereo -> Mono
float sample = 0;
sample += frames[(pMP3->framesConsumed*pMP3->frameChannels)+0];
sample += frames[(pMP3->framesConsumed*pMP3->frameChannels)+1];
sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0];
sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1];
pFramesOutF[0] = sample * 0.5f;
} else {
// Stereo -> Stereo
pFramesOutF[0] = frames[(pMP3->framesConsumed*pMP3->frameChannels)+0];
pFramesOutF[1] = frames[(pMP3->framesConsumed*pMP3->frameChannels)+1];
pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0];
pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1];
}
}
#endif
pMP3->framesConsumed += 1;
pMP3->framesRemaining -= 1;
frameCount -= 1;
pMP3->pcmFramesConsumedInMP3Frame += 1;
pMP3->pcmFramesRemainingInMP3Frame -= 1;
totalFramesRead += 1;
frameCount -= 1;
pFramesOutF += pSRC->config.channels;
}
......@@ -2530,11 +2653,11 @@ static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, voi
break;
}
drmp3_assert(pMP3->framesRemaining == 0);
drmp3_assert(pMP3->pcmFramesRemainingInMP3Frame == 0);
// At this point we have exhausted our in-memory buffer so we need to re-fill. Note that the sample rate may have changed
// at this point which means we'll also need to update our sample rate conversion pipeline.
if (!drmp3_decode_next_frame(pMP3)) {
if (drmp3_decode_next_frame(pMP3) == 0) {
break;
}
}
......@@ -2710,7 +2833,9 @@ drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* filePath, const drmp3_conf
void drmp3_uninit(drmp3* pMP3)
{
if (pMP3 == NULL) return;
if (pMP3 == NULL) {
return;
}
#ifndef DR_MP3_NO_STDIO
if (pMP3->onRead == drmp3__on_read_stdio) {
......@@ -2721,9 +2846,11 @@ void drmp3_uninit(drmp3* pMP3)
drmp3_free(pMP3->pData);
}
drmp3_uint64 drmp3_read_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut)
drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut)
{
if (pMP3 == NULL || pMP3->onRead == NULL) return 0;
if (pMP3 == NULL || pMP3->onRead == NULL) {
return 0;
}
drmp3_uint64 totalFramesRead = 0;
......@@ -2735,7 +2862,7 @@ drmp3_uint64 drmp3_read_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBuff
framesToReadRightNow = framesToRead;
}
drmp3_uint64 framesJustRead = drmp3_read_f32(pMP3, framesToReadRightNow, temp);
drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp);
if (framesJustRead == 0) {
break;
}
......@@ -2745,40 +2872,471 @@ drmp3_uint64 drmp3_read_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBuff
}
} else {
totalFramesRead = drmp3_src_read_frames_ex(&pMP3->src, framesToRead, pBufferOut, DRMP3_TRUE);
pMP3->currentPCMFrame += totalFramesRead;
}
return totalFramesRead;
}
drmp3_bool32 drmp3_seek_to_frame(drmp3* pMP3, drmp3_uint64 frameIndex)
void drmp3_reset(drmp3* pMP3)
{
drmp3_assert(pMP3 != NULL);
pMP3->pcmFramesConsumedInMP3Frame = 0;
pMP3->pcmFramesRemainingInMP3Frame = 0;
pMP3->currentPCMFrame = 0;
pMP3->dataSize = 0;
pMP3->atEnd = DRMP3_FALSE;
pMP3->src.bin[0] = 0;
pMP3->src.bin[1] = 0;
pMP3->src.bin[2] = 0;
pMP3->src.bin[3] = 0;
pMP3->src.cache.cachedFrameCount = 0;
pMP3->src.cache.iNextFrame = 0;
pMP3->src.algo.linear.alpha = 0;
pMP3->src.algo.linear.isNextFramesLoaded = 0;
pMP3->src.algo.linear.isPrevFramesLoaded = 0;
//drmp3_zero_object(&pMP3->decoder);
drmp3dec_init(&pMP3->decoder);
}
drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3)
{
if (pMP3 == NULL || pMP3->onSeek == NULL) return DRMP3_FALSE;
drmp3_assert(pMP3 != NULL);
drmp3_assert(pMP3->onSeek != NULL);
// Seek to the start of the stream to begin with.
if (!pMP3->onSeek(pMP3->pUserData, 0, drmp3_seek_origin_start)) {
if (!drmp3__on_seek(pMP3, 0, drmp3_seek_origin_start)) {
return DRMP3_FALSE;
}
// Clear any cached data.
pMP3->framesConsumed = 0;
pMP3->framesRemaining = 0;
pMP3->dataSize = 0;
pMP3->atEnd = DRMP3_FALSE;
drmp3_reset(pMP3);
return DRMP3_TRUE;
}
float drmp3_get_cached_pcm_frame_count_from_src(drmp3* pMP3)
{
return (pMP3->src.cache.cachedFrameCount - pMP3->src.cache.iNextFrame) + (float)pMP3->src.algo.linear.alpha;
}
float drmp3_get_pcm_frames_remaining_in_mp3_frame(drmp3* pMP3)
{
float factor = (float)pMP3->src.config.sampleRateOut / (float)pMP3->src.config.sampleRateIn;
float frameCountPreSRC = drmp3_get_cached_pcm_frame_count_from_src(pMP3) + pMP3->pcmFramesRemainingInMP3Frame;
return frameCountPreSRC * factor;
}
// NOTE ON SEEKING
// ===============
// The seeking code below is a complete mess and is broken for cases when the sample rate changes. The problem
// is with the resampling and the crappy resampler used by dr_mp3. What needs to happen is the following:
//
// 1) The resampler needs to be replaced.
// 2) The resampler has state which needs to be updated whenever an MP3 frame is decoded outside of
// drmp3_read_pcm_frames_f32(). The resampler needs an API to "flush" some imaginary input so that it's
// state is updated accordingly.
drmp3_bool32 drmp3_seek_forward_by_pcm_frames__brute_force(drmp3* pMP3, drmp3_uint64 frameOffset)
{
#if 0
// MP3 is a bit annoying when it comes to seeking because of the bit reservoir. It basically means that an MP3 frame can possibly
// depend on some of the data of prior frames. This means it's not as simple as seeking to the first byte of the MP3 frame that
// contains the sample because that MP3 frame will need the data from the previous MP3 frame (which we just seeked past!). To
// resolve this we seek past a number of MP3 frames up to a point, and then read-and-discard the remainder.
drmp3_uint64 maxFramesToReadAndDiscard = (drmp3_uint64)(DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME * 3 * ((float)pMP3->src.config.sampleRateOut / (float)pMP3->src.config.sampleRateIn));
// Now get rid of leading whole frames.
while (frameOffset > maxFramesToReadAndDiscard) {
float pcmFramesRemainingInCurrentMP3FrameF = drmp3_get_pcm_frames_remaining_in_mp3_frame(pMP3);
drmp3_uint32 pcmFramesRemainingInCurrentMP3Frame = (drmp3_uint32)pcmFramesRemainingInCurrentMP3FrameF;
if (frameOffset > pcmFramesRemainingInCurrentMP3Frame) {
frameOffset -= pcmFramesRemainingInCurrentMP3Frame;
pMP3->currentPCMFrame += pcmFramesRemainingInCurrentMP3Frame;
pMP3->pcmFramesConsumedInMP3Frame += pMP3->pcmFramesRemainingInMP3Frame;
pMP3->pcmFramesRemainingInMP3Frame = 0;
} else {
break;
}
drmp3_uint32 pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, pMP3->pcmFrames, DRMP3_FALSE);
if (pcmFrameCount == 0) {
break;
}
}
// The last step is to read-and-discard any remaining PCM frames to make it sample-exact.
drmp3_uint64 framesRead = drmp3_read_pcm_frames_f32(pMP3, frameOffset, NULL);
if (framesRead != frameOffset) {
return DRMP3_FALSE;
}
#else
// Just using a dumb read-and-discard for now pending updates to the resampler.
drmp3_uint64 framesRead = drmp3_read_pcm_frames_f32(pMP3, frameOffset, NULL);
if (framesRead != frameOffset) {
return DRMP3_FALSE;
}
#endif
return DRMP3_TRUE;
}
drmp3_bool32 drmp3_seek_to_pcm_frame__brute_force(drmp3* pMP3, drmp3_uint64 frameIndex)
{
drmp3_assert(pMP3 != NULL);
if (frameIndex == pMP3->currentPCMFrame) {
return DRMP3_TRUE;
}
// If we're moving foward we just read from where we're at. Otherwise we need to move back to the start of
// the stream and read from the beginning.
//drmp3_uint64 framesToReadAndDiscard;
if (frameIndex < pMP3->currentPCMFrame) {
// Moving backward. Move to the start of the stream and then move forward.
if (!drmp3_seek_to_start_of_stream(pMP3)) {
return DRMP3_FALSE;
}
}
drmp3_assert(frameIndex >= pMP3->currentPCMFrame);
return drmp3_seek_forward_by_pcm_frames__brute_force(pMP3, (frameIndex - pMP3->currentPCMFrame));
}
drmp3_bool32 drmp3_find_closest_seek_point(drmp3* pMP3, drmp3_uint64 frameIndex, drmp3_uint32* pSeekPointIndex)
{
drmp3_assert(pSeekPointIndex != NULL);
if (frameIndex < pMP3->pSeekPoints[0].pcmFrameIndex) {
return DRMP3_FALSE;
}
// Linear search for simplicity to begin with while I'm getting this thing working. Once it's all working change this to a binary search.
for (drmp3_uint32 iSeekPoint = 0; iSeekPoint < pMP3->seekPointCount; ++iSeekPoint) {
if (pMP3->pSeekPoints[iSeekPoint].pcmFrameIndex > frameIndex) {
break; // Found it.
}
*pSeekPointIndex = iSeekPoint;
}
return DRMP3_TRUE;
}
drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frameIndex)
{
drmp3_assert(pMP3 != NULL);
drmp3_assert(pMP3->pSeekPoints != NULL);
drmp3_assert(pMP3->seekPointCount > 0);
drmp3_seek_point seekPoint;
// If there is no prior seekpoint it means the target PCM frame comes before the first seek point. Just assume a seekpoint at the start of the file in this case.
drmp3_uint32 priorSeekPointIndex;
if (drmp3_find_closest_seek_point(pMP3, frameIndex, &priorSeekPointIndex)) {
seekPoint = pMP3->pSeekPoints[priorSeekPointIndex];
} else {
seekPoint.seekPosInBytes = 0;
seekPoint.pcmFrameIndex = 0;
seekPoint.mp3FramesToDiscard = 0;
seekPoint.pcmFramesToDiscard = 0;
}
// First thing to do is seek to the first byte of the relevant MP3 frame.
if (!drmp3__on_seek_64(pMP3, seekPoint.seekPosInBytes, drmp3_seek_origin_start)) {
return DRMP3_FALSE; // Failed to seek.
}
// Clear any cached data.
drmp3_reset(pMP3);
// Whole MP3 frames need to be discarded first.
for (drmp3_uint16 iMP3Frame = 0; iMP3Frame < seekPoint.mp3FramesToDiscard; ++iMP3Frame) {
// Pass in non-null for the last frame because we want to ensure the sample rate converter is preloaded correctly.
drmp3d_sample_t* pPCMFrames = NULL;
if (iMP3Frame == seekPoint.mp3FramesToDiscard-1) {
pPCMFrames = (drmp3d_sample_t*)pMP3->pcmFrames;
}
// We first need to decode the next frame, and then we need to flush the resampler.
drmp3_uint32 pcmFramesReadPreSRC = drmp3_decode_next_frame_ex(pMP3, pPCMFrames, DRMP3_TRUE);
if (pcmFramesReadPreSRC == 0) {
return DRMP3_FALSE;
}
}
// We seeked to an MP3 frame in the raw stream so we need to make sure the current PCM frame is set correctly.
pMP3->currentPCMFrame = seekPoint.pcmFrameIndex - seekPoint.pcmFramesToDiscard;
// Update resampler. This is wrong. Need to instead update it on a per MP3 frame basis. Also broken for cases when
// the sample rate is being reduced in my testing. Should work fine when the input and output sample rate is the same
// or a clean multiple.
pMP3->src.algo.linear.alpha = pMP3->currentPCMFrame * ((double)pMP3->src.config.sampleRateIn / pMP3->src.config.sampleRateOut);
pMP3->src.algo.linear.alpha = pMP3->src.algo.linear.alpha - (drmp3_uint32)(pMP3->src.algo.linear.alpha);
if (pMP3->src.algo.linear.alpha > 0) {
pMP3->src.algo.linear.isPrevFramesLoaded = 1;
}
// Now at this point we can follow the same process as the brute force technique where we just skip over unnecessary MP3 frames and then
// read-and-discard at least 2 whole MP3 frames.
drmp3_uint64 leftoverFrames = frameIndex - pMP3->currentPCMFrame;
return drmp3_seek_forward_by_pcm_frames__brute_force(pMP3, leftoverFrames);
}
drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex)
{
if (pMP3 == NULL || pMP3->onSeek == NULL) {
return DRMP3_FALSE;
}
if (frameIndex == 0) {
return drmp3_seek_to_start_of_stream(pMP3);
}
// Use the seek table if we have one.
if (pMP3->pSeekPoints != NULL && pMP3->seekPointCount > 0) {
return drmp3_seek_to_pcm_frame__seek_table(pMP3, frameIndex);
} else {
return drmp3_seek_to_pcm_frame__brute_force(pMP3, frameIndex);
}
}
drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3FrameCount, drmp3_uint64* pPCMFrameCount)
{
if (pMP3 == NULL) {
return DRMP3_FALSE;
}
// The way this works is we move back to the start of the stream, iterate over each MP3 frame and calculate the frame count based
// on our output sample rate, the seek back to the PCM frame we were sitting on before calling this function.
// The stream must support seeking for this to work.
if (pMP3->onSeek == NULL) {
return DRMP3_FALSE;
}
// We'll need to seek back to where we were, so grab the PCM frame we're currently sitting on so we can restore later.
drmp3_uint64 currentPCMFrame = pMP3->currentPCMFrame;
if (!drmp3_seek_to_start_of_stream(pMP3)) {
return DRMP3_FALSE;
}
drmp3_uint64 totalPCMFrameCount = 0;
drmp3_uint64 totalMP3FrameCount = 0;
float totalPCMFrameCountFractionalPart = 0; // <-- With resampling there will be a fractional part to each MP3 frame that we need to accumulate.
for (;;) {
drmp3_uint32 pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_FALSE);
if (pcmFramesInCurrentMP3FrameIn == 0) {
break;
}
float srcRatio = (float)pMP3->mp3FrameSampleRate / (float)pMP3->sampleRate;
drmp3_assert(srcRatio > 0);
float pcmFramesInCurrentMP3FrameOutF = totalPCMFrameCountFractionalPart + (pcmFramesInCurrentMP3FrameIn / srcRatio);
drmp3_uint32 pcmFramesInCurrentMP3FrameOut = (drmp3_uint32)pcmFramesInCurrentMP3FrameOutF;
totalPCMFrameCountFractionalPart = pcmFramesInCurrentMP3FrameOutF - pcmFramesInCurrentMP3FrameOut;
totalPCMFrameCount += pcmFramesInCurrentMP3FrameOut;
totalMP3FrameCount += 1;
}
// TODO: Optimize.
//
// This is inefficient. We simply read frames from the start of the stream.
drmp3_uint64 framesRead = drmp3_read_f32(pMP3, frameIndex, NULL);
if (framesRead != frameIndex) {
// Finally, we need to seek back to where we were.
if (!drmp3_seek_to_start_of_stream(pMP3)) {
return DRMP3_FALSE;
}
if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) {
return DRMP3_FALSE;
}
if (pMP3FrameCount != NULL) {
*pMP3FrameCount = totalMP3FrameCount;
}
if (pPCMFrameCount != NULL) {
*pPCMFrameCount = totalPCMFrameCount;
}
return DRMP3_TRUE;
}
drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3)
{
drmp3_uint64 totalPCMFrameCount;
if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, NULL, &totalPCMFrameCount)) {
return 0;
}
return totalPCMFrameCount;
}
float* drmp3__full_decode_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3)
{
drmp3_uint64 totalMP3FrameCount;
if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, &totalMP3FrameCount, NULL)) {
return 0;
}
return totalMP3FrameCount;
}
void drmp3__accumulate_running_pcm_frame_count(drmp3* pMP3, drmp3_uint32 pcmFrameCountIn, drmp3_uint64* pRunningPCMFrameCount, float* pRunningPCMFrameCountFractionalPart)
{
float srcRatio = (float)pMP3->mp3FrameSampleRate / (float)pMP3->sampleRate;
drmp3_assert(srcRatio > 0);
float pcmFrameCountOutF = *pRunningPCMFrameCountFractionalPart + (pcmFrameCountIn / srcRatio);
drmp3_uint32 pcmFrameCountOut = (drmp3_uint32)pcmFrameCountOutF;
*pRunningPCMFrameCountFractionalPart = pcmFrameCountOutF - pcmFrameCountOut;
*pRunningPCMFrameCount += pcmFrameCountOut;
}
typedef struct
{
drmp3_uint64 bytePos;
drmp3_uint64 pcmFrameIndex; // <-- After sample rate conversion.
} drmp3__seeking_mp3_frame_info;
drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints)
{
if (pMP3 == NULL || pSeekPointCount == NULL || pSeekPoints == NULL) {
return DRMP3_FALSE; // Invalid args.
}
drmp3_uint32 seekPointCount = *pSeekPointCount;
if (seekPointCount == 0) {
return DRMP3_FALSE; // The client has requested no seek points. Consider this to be invalid arguments since the client has probably not intended this.
}
// We'll need to seek back to the current sample after calculating the seekpoints so we need to go ahead and grab the current location at the top.
drmp3_uint64 currentPCMFrame = pMP3->currentPCMFrame;
// We never do more than the total number of MP3 frames and we limit it to 32-bits.
drmp3_uint64 totalMP3FrameCount;
drmp3_uint64 totalPCMFrameCount;
if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, &totalMP3FrameCount, &totalPCMFrameCount)) {
return DRMP3_FALSE;
}
// If there's less than DRMP3_SEEK_LEADING_MP3_FRAMES+1 frames we just report 1 seek point which will be the very start of the stream.
if (totalMP3FrameCount < DRMP3_SEEK_LEADING_MP3_FRAMES+1) {
seekPointCount = 1;
pSeekPoints[0].seekPosInBytes = 0;
pSeekPoints[0].pcmFrameIndex = 0;
pSeekPoints[0].mp3FramesToDiscard = 0;
pSeekPoints[0].pcmFramesToDiscard = 0;
} else {
if (seekPointCount > totalMP3FrameCount-1) {
seekPointCount = (drmp3_uint32)totalMP3FrameCount-1;
}
drmp3_uint64 pcmFramesBetweenSeekPoints = totalPCMFrameCount / (seekPointCount+1);
// Here is where we actually calculate the seek points. We need to start by moving the start of the stream. We then enumerate over each
// MP3 frame.
if (!drmp3_seek_to_start_of_stream(pMP3)) {
return DRMP3_FALSE;
}
// We need to cache the byte positions of the previous MP3 frames. As a new MP3 frame is iterated, we cycle the byte positions in this
// array. The value in the first item in this array is the byte position that will be reported in the next seek point.
drmp3__seeking_mp3_frame_info mp3FrameInfo[DRMP3_SEEK_LEADING_MP3_FRAMES+1];
drmp3_uint64 runningPCMFrameCount = 0;
float runningPCMFrameCountFractionalPart = 0;
// We need to initialize the array of MP3 byte positions for the leading MP3 frames.
for (int iMP3Frame = 0; iMP3Frame < DRMP3_SEEK_LEADING_MP3_FRAMES+1; ++iMP3Frame) {
// The byte position of the next frame will be the stream's cursor position, minus whatever is sitting in the buffer.
drmp3_assert(pMP3->streamCursor >= pMP3->dataSize);
mp3FrameInfo[iMP3Frame].bytePos = pMP3->streamCursor - pMP3->dataSize;
mp3FrameInfo[iMP3Frame].pcmFrameIndex = runningPCMFrameCount;
// We need to get information about this frame so we can know how many samples it contained.
drmp3_uint32 pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_FALSE);
if (pcmFramesInCurrentMP3FrameIn == 0) {
return DRMP3_FALSE; // This should never happen.
}
drmp3__accumulate_running_pcm_frame_count(pMP3, pcmFramesInCurrentMP3FrameIn, &runningPCMFrameCount, &runningPCMFrameCountFractionalPart);
}
// At this point we will have extracted the byte positions of the leading MP3 frames. We can now start iterating over each seek point and
// calculate them.
drmp3_uint64 nextTargetPCMFrame = 0;
for (drmp3_uint32 iSeekPoint = 0; iSeekPoint < seekPointCount; ++iSeekPoint) {
nextTargetPCMFrame += pcmFramesBetweenSeekPoints;
for (;;) {
if (nextTargetPCMFrame < runningPCMFrameCount) {
// The next seek point is in the current MP3 frame.
pSeekPoints[iSeekPoint].seekPosInBytes = mp3FrameInfo[0].bytePos;
pSeekPoints[iSeekPoint].pcmFrameIndex = nextTargetPCMFrame;
pSeekPoints[iSeekPoint].mp3FramesToDiscard = DRMP3_SEEK_LEADING_MP3_FRAMES;
pSeekPoints[iSeekPoint].pcmFramesToDiscard = (drmp3_uint16)(nextTargetPCMFrame - mp3FrameInfo[DRMP3_SEEK_LEADING_MP3_FRAMES-1].pcmFrameIndex);
break;
} else {
// The next seek point is not in the current MP3 frame, so continue on to the next one. The first thing to do is cycle the cached
// MP3 frame info.
for (int i = 0; i < drmp3_countof(mp3FrameInfo)-1; ++i) {
mp3FrameInfo[i] = mp3FrameInfo[i+1];
}
// Cache previous MP3 frame info.
mp3FrameInfo[drmp3_countof(mp3FrameInfo)-1].bytePos = pMP3->streamCursor - pMP3->dataSize;
mp3FrameInfo[drmp3_countof(mp3FrameInfo)-1].pcmFrameIndex = runningPCMFrameCount;
// Go to the next MP3 frame. This shouldn't ever fail, but just in case it does we just set the seek point and break. If it happens, it
// should only ever do it for the last seek point.
drmp3_uint32 pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_TRUE);
if (pcmFramesInCurrentMP3FrameIn == 0) {
pSeekPoints[iSeekPoint].seekPosInBytes = mp3FrameInfo[0].bytePos;
pSeekPoints[iSeekPoint].pcmFrameIndex = nextTargetPCMFrame;
pSeekPoints[iSeekPoint].mp3FramesToDiscard = DRMP3_SEEK_LEADING_MP3_FRAMES;
pSeekPoints[iSeekPoint].pcmFramesToDiscard = (drmp3_uint16)(nextTargetPCMFrame - mp3FrameInfo[DRMP3_SEEK_LEADING_MP3_FRAMES-1].pcmFrameIndex);
break;
}
drmp3__accumulate_running_pcm_frame_count(pMP3, pcmFramesInCurrentMP3FrameIn, &runningPCMFrameCount, &runningPCMFrameCountFractionalPart);
}
}
}
// Finally, we need to seek back to where we were.
if (!drmp3_seek_to_start_of_stream(pMP3)) {
return DRMP3_FALSE;
}
if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) {
return DRMP3_FALSE;
}
}
*pSeekPointCount = seekPointCount;
return DRMP3_TRUE;
}
drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints)
{
if (pMP3 == NULL) {
return DRMP3_FALSE;
}
if (seekPointCount == 0 || pSeekPoints == NULL) {
// Unbinding.
pMP3->seekPointCount = 0;
pMP3->pSeekPoints = NULL;
} else {
// Binding.
pMP3->seekPointCount = seekPointCount;
pMP3->pSeekPoints = pSeekPoints;
}
return DRMP3_TRUE;
}
float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
{
drmp3_assert(pMP3 != NULL);
......@@ -2789,7 +3347,7 @@ float* drmp3__full_decode_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp
float temp[4096];
for (;;) {
drmp3_uint64 framesToReadRightNow = drmp3_countof(temp) / pMP3->channels;
drmp3_uint64 framesJustRead = drmp3_read_f32(pMP3, framesToReadRightNow, temp);
drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp);
if (framesJustRead == 0) {
break;
}
......@@ -2835,35 +3393,35 @@ float* drmp3__full_decode_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp
return pFrames;
}
float* drmp3_open_and_decode_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
float* drmp3_open_and_read_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
{
drmp3 mp3;
if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pConfig)) {
return NULL;
}
return drmp3__full_decode_and_close_f32(&mp3, pConfig, pTotalFrameCount);
return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount);
}
float* drmp3_open_and_decode_memory_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
float* drmp3_open_memory_and_read_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
{
drmp3 mp3;
if (!drmp3_init_memory(&mp3, pData, dataSize, pConfig)) {
return NULL;
}
return drmp3__full_decode_and_close_f32(&mp3, pConfig, pTotalFrameCount);
return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount);
}
#ifndef DR_MP3_NO_STDIO
float* drmp3_open_and_decode_file_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
float* drmp3_open_file_and_read_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount)
{
drmp3 mp3;
if (!drmp3_init_file(&mp3, filePath, pConfig)) {
return NULL;
}
return drmp3__full_decode_and_close_f32(&mp3, pConfig, pTotalFrameCount);
return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount);
}
#endif
......@@ -2890,7 +3448,18 @@ void drmp3_free(void* p)
// REVISION HISTORY
// ===============
// ================
//
// v0.4.0 - 2018-xx-xx
// - API CHANGE: Rename some APIs:
// - drmp3_read_f32 -> to drmp3_read_pcm_frames_f32
// - drmp3_seek_to_frame -> drmp3_seek_to_pcm_frame
// - drmp3_open_and_decode_f32 -> drmp3_open_and_read_f32
// - drmp3_open_and_decode_memory_f32 -> drmp3_open_memory_and_read_f32
// - drmp3_open_and_decode_file_f32 -> drmp3_open_file_and_read_f32
// - Add drmp3_get_pcm_frame_count().
// - Add drmp3_get_mp3_frame_count().
// - Improve seeking performance.
//
// v0.3.2 - 2018-09-11
// - Fix a couple of memory leaks.
......
// WAV audio loader and writer. Public domain. See "unlicense" statement at the end of this file.
// dr_wav - v0.8.5 - 2018-09-11
// dr_wav - v0.9.0-dev - 2018-xx-xx
//
// David Reid - mackron@gmail.com
......@@ -40,7 +40,7 @@
// unsigned int channels;
// unsigned int sampleRate;
// drwav_uint64 totalSampleCount;
// float* pSampleData = drwav_open_and_read_file_s32("my_song.wav", &channels, &sampleRate, &totalSampleCount);
// float* pSampleData = drwav_open_file_and_read_f32("my_song.wav", &channels, &sampleRate, &totalSampleCount);
// if (pSampleData == NULL) {
// // Error opening and reading WAV file.
// }
......@@ -154,6 +154,14 @@ extern "C" {
#define DR_WAVE_FORMAT_DVI_ADPCM 0x11
#define DR_WAVE_FORMAT_EXTENSIBLE 0xFFFE
// Constants.
#ifndef DRWAV_MAX_SMPL_LOOPS
#define DRWAV_MAX_SMPL_LOOPS 1
#endif
// Flags to pass into drwav_init_ex(), etc.
#define DRWAV_SEQUENTIAL 0x00000001
typedef enum
{
drwav_seek_origin_start,
......@@ -166,6 +174,22 @@ typedef enum
drwav_container_w64
} drwav_container;
typedef struct
{
union
{
drwav_uint8 fourcc[4];
drwav_uint8 guid[16];
} id;
// The size in bytes of the chunk.
drwav_uint64 sizeInBytes;
// RIFF = 2 byte alignment.
// W64 = 8 byte alignment.
unsigned int paddingSize;
} drwav_chunk_header;
// Callback for when data is read. Return value is the number of bytes actually read.
//
// pUserData [in] The user data that was passed to drwav_init(), drwav_open() and family.
......@@ -201,6 +225,22 @@ typedef size_t (* drwav_write_proc)(void* pUserData, const void* pData, size_t b
// will be either drwav_seek_origin_start or drwav_seek_origin_current.
typedef drwav_bool32 (* drwav_seek_proc)(void* pUserData, int offset, drwav_seek_origin origin);
// Callback for when drwav_init_ex/drwav_open_ex finds a chunk.
//
// pChunkUserData [in] The user data that was passed to the pChunkUserData parameter of drwav_init_ex(), drwav_open_ex() and family.
// onRead [in] A pointer to the function to call when reading.
// onSeek [in] A pointer to the function to call when seeking.
// pReadSeekUserData [in] The user data that was passed to the pReadSeekUserData parameter of drwav_init_ex(), drwav_open_ex() and family.
// pChunkHeader [in] A pointer to an object containing basic header information about the chunk. Use this to identify the chunk.
//
// Returns the number of bytes read + seeked.
//
// To read data from the chunk, call onRead(), passing in pReadSeekUserData as the first parameter. Do the same
// for seeking with onSeek(). The return value must be the total number of bytes you have read _plus_ seeked.
//
// You must not attempt to read beyond the boundary of the chunk.
typedef drwav_uint64 (* drwav_chunk_proc)(void* pChunkUserData, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_chunk_header* pChunkHeader);
// Structure for internal use. Only used for loaders opened with drwav_open_memory().
typedef struct
{
......@@ -264,6 +304,30 @@ typedef struct
drwav_uint8 subFormat[16];
} drwav_fmt;
typedef struct
{
drwav_uint32 cuePointId;
drwav_uint32 type;
drwav_uint32 start;
drwav_uint32 end;
drwav_uint32 fraction;
drwav_uint32 playCount;
} drwav_smpl_loop;
typedef struct
{
drwav_uint32 manufacturer;
drwav_uint32 product;
drwav_uint32 samplePeriod;
drwav_uint32 midiUnityNotes;
drwav_uint32 midiPitchFraction;
drwav_uint32 smpteFormat;
drwav_uint32 smpteOffset;
drwav_uint32 numSampleLoops;
drwav_uint32 samplerData;
drwav_smpl_loop loops[DRWAV_MAX_SMPL_LOOPS];
} drwav_smpl;
typedef struct
{
// A pointer to the function to call when more data is needed.
......@@ -324,6 +388,10 @@ typedef struct
drwav_bool32 isSequentialWrite;
// smpl chunk.
drwav_smpl smpl;
// A hack to avoid a DRWAV_MALLOC() when opening a decoder with drwav_open_memory().
drwav__memory_stream memoryStream;
drwav__memory_stream_write memoryStreamWrite;
......@@ -359,9 +427,13 @@ typedef struct
// Initializes a pre-allocated drwav object.
//
// onRead [in] The function to call when data needs to be read from the client.
// onSeek [in] The function to call when the read position of the client data needs to move.
// pUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek.
// pWav [out] A pointer to the drwav object being initialized.
// onRead [in] The function to call when data needs to be read from the client.
// onSeek [in] The function to call when the read position of the client data needs to move.
// onChunk [in, optional] The function to call when a chunk is enumerated at initialized time.
// pUserData, pReadSeekUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek.
// pChunkUserData [in, optional] A pointer to application defined data that will be passed to onChunk.
// flags [in, optional] A set of flags for controlling how things are loaded.
//
// Returns true if successful; false otherwise.
//
......@@ -373,8 +445,18 @@ typedef struct
// If you want dr_wav to manage the memory allocation for you, consider using drwav_open() instead. This will allocate
// a drwav object on the heap and return a pointer to it.
//
// Possible values for flags:
// DRWAV_SEQUENTIAL: Never perform a backwards seek while loading. This disables the chunk callback and will cause this function
// to return as soon as the data chunk is found. Any chunks after the data chunk will be ignored.
//
// drwav_init() is equivalent to "drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0);".
//
// The onChunk is callback is not called for the WAVE or FMT chunks. The contents of the FMT chunk can be read from pWav->fmt
// after the function returns.
//
// See also: drwav_init_file(), drwav_init_memory(), drwav_uninit()
drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData);
drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags);
// Initializes a pre-allocated drwav object for writing.
//
......@@ -421,8 +503,9 @@ void drwav_uninit(drwav* pWav);
// This is different from drwav_init() in that it will allocate the drwav object for you via DRWAV_MALLOC() before
// initializing it.
//
// See also: drwav_open_file(), drwav_open_memory(), drwav_close()
// See also: drwav_init(), drwav_open_file(), drwav_open_memory(), drwav_close()
drwav* drwav_open(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData);
drwav* drwav_open_ex(drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags);
// Opens a wav file for writing using the given callbacks.
//
......@@ -473,11 +556,18 @@ size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut);
// using a compressed format consider using drwav_read_raw() or drwav_read_s16/s32/f32/etc().
drwav_uint64 drwav_read(drwav* pWav, drwav_uint64 samplesToRead, void* pBufferOut);
// Same as drwav_read(), except works on PCM frames instead of samples. Returns the number of PCM
// freames read.
drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut);
// Seeks to the given sample.
//
// Returns true if successful; false otherwise.
drwav_bool32 drwav_seek_to_sample(drwav* pWav, drwav_uint64 sample);
// Same as drwav_seek_to_sample() except workd on PCM frames.
drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex);
// Writes raw audio data.
//
......@@ -489,6 +579,8 @@ size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData);
// Returns the number of samples written.
drwav_uint64 drwav_write(drwav* pWav, drwav_uint64 samplesToWrite, const void* pData);
// Same as drwav_write(), but works on PCM frames instead of samples. Returns the number of PCM frames written.
drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData);
//// Conversion Utilities ////
......@@ -501,6 +593,9 @@ drwav_uint64 drwav_write(drwav* pWav, drwav_uint64 samplesToWrite, const void* p
// If the return value is less than <samplesToRead> it means the end of the file has been reached.
drwav_uint64 drwav_read_s16(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut);
// Same as drwav_read_s16(), except works on PCM frames instead of samples.
drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut);
// Low-level function for converting unsigned 8-bit PCM samples to signed 16-bit PCM samples.
void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount);
......@@ -530,6 +625,9 @@ void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sample
// If the return value is less than <samplesToRead> it means the end of the file has been reached.
drwav_uint64 drwav_read_f32(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut);
// Same as drwav_read_f32(), except works on PCM frames instead of samples.
drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut);
// Low-level function for converting unsigned 8-bit PCM samples to IEEE 32-bit floating point samples.
void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount);
......@@ -559,6 +657,9 @@ void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount)
// If the return value is less than <samplesToRead> it means the end of the file has been reached.
drwav_uint64 drwav_read_s32(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut);
// Same as drwav_read_s32(), except works on PCM frames instead of samples.
drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut);
// Low-level function for converting unsigned 8-bit PCM samples to signed 32-bit PCM samples.
void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount);
......@@ -593,6 +694,7 @@ void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sample
// objects because the operating system may restrict the number of file handles an application can have open at
// any given time.
drwav_bool32 drwav_init_file(drwav* pWav, const char* filename);
drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags);
// Helper for initializing a wave file for writing using stdio.
//
......@@ -608,6 +710,7 @@ drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename,
// objects because the operating system may restrict the number of file handles an application can have open at
// any given time.
drwav* drwav_open_file(const char* filename);
drwav* drwav_open_file_ex(const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags);
// Helper for opening a wave file for writing using stdio.
//
......@@ -626,6 +729,7 @@ drwav* drwav_open_file_write_sequential(const char* filename, const drwav_data_f
//
// The buffer should contain the contents of the entire wave file, not just the sample data.
drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize);
drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags);
// Helper for initializing a writer which outputs data to a memory buffer.
//
......@@ -643,6 +747,7 @@ drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size
//
// The buffer should contain the contents of the entire wave file, not just the sample data.
drwav* drwav_open_memory(const void* data, size_t dataSize);
drwav* drwav_open_memory_ex(const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags);
// Helper for opening a writer which outputs data to a memory buffer.
//
......@@ -657,19 +762,28 @@ drwav* drwav_open_memory_write_sequential(void** ppData, size_t* pDataSize, cons
#ifndef DR_WAV_NO_CONVERSION_API
// Opens and reads a wav file in a single operation.
drwav_int16* drwav_open_and_read_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount);
drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount);
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);
float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount);
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);
drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount);
#ifndef DR_WAV_NO_STDIO
// 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);
drwav_int16* drwav_open_file_and_read_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount);
drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount);
float* drwav_open_file_and_read_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount);
float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount);
drwav_int32* drwav_open_file_and_read_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount);
drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount);
#endif
// 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);
drwav_int16* drwav_open_memory_and_read_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount);
drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount);
float* drwav_open_memory_and_read_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount);
float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount);
drwav_int32* drwav_open_memory_and_read_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount);
drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount);
#endif
// Frees data that was allocated internally by dr_wav.
......@@ -727,6 +841,13 @@ void drwav_free(void* pDataReturnedByOpenAndRead);
#define drwav_copy_memory DRWAV_COPY_MEMORY
#define drwav_zero_memory DRWAV_ZERO_MEMORY
typedef drwav_int32 drwav_result;
#define DRWAV_SUCCESS 0
#define DRWAV_ERROR -1
#define DRWAV_INVALID_ARGS -2
#define DRWAV_INVALID_OPERATION -3
#define DRWAV_INVALID_FILE -100
#define DRWAV_EOF -101
#define DRWAV_MAX_SIMD_VECTOR_SIZE 64 // 64 for AVX-512 in the future.
......@@ -756,6 +877,7 @@ static const drwav_uint8 drwavGUID_W64_JUNK[16] = {0x6A,0x75,0x6E,0x6B, 0xF3,0xA
static const drwav_uint8 drwavGUID_W64_FMT [16] = {0x66,0x6D,0x74,0x20, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 20746D66-ACF3-11D3-8CD1-00C04F8EDB8A
static const drwav_uint8 drwavGUID_W64_FACT[16] = {0x66,0x61,0x63,0x74, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 74636166-ACF3-11D3-8CD1-00C04F8EDB8A
static const drwav_uint8 drwavGUID_W64_DATA[16] = {0x64,0x61,0x74,0x61, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 61746164-ACF3-11D3-8CD1-00C04F8EDB8A
static const drwav_uint8 drwavGUID_W64_SMPL[16] = {0x73,0x6D,0x70,0x6C, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 6C706D73-ACF3-11D3-8CD1-00C04F8EDB8A
static DRWAV_INLINE drwav_bool32 drwav__guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16])
{
......@@ -823,39 +945,21 @@ static DRWAV_INLINE drwav_bool32 drwav__is_compressed_format_tag(drwav_uint16 fo
formatTag == DR_WAVE_FORMAT_DVI_ADPCM;
}
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);
drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData);
drwav* drwav_open_write__internal(const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData);
typedef struct
{
union
{
drwav_uint8 fourcc[4];
drwav_uint8 guid[16];
} id;
// The size in bytes of the chunk.
drwav_uint64 sizeInBytes;
// RIFF = 2 byte alignment.
// W64 = 8 byte alignment.
unsigned int paddingSize;
} drwav__chunk_header;
static drwav_bool32 drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav__chunk_header* pHeaderOut)
static drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut)
{
if (container == drwav_container_riff) {
if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) {
return DRWAV_FALSE;
return DRWAV_EOF;
}
unsigned char sizeInBytes[4];
if (onRead(pUserData, sizeInBytes, 4) != 4) {
return DRWAV_FALSE;
return DRWAV_INVALID_FILE;
}
pHeaderOut->sizeInBytes = drwav__bytes_to_u32(sizeInBytes);
......@@ -863,12 +967,12 @@ static drwav_bool32 drwav__read_chunk_header(drwav_read_proc onRead, void* pUser
*pRunningBytesReadOut += 8;
} else {
if (onRead(pUserData, pHeaderOut->id.guid, 16) != 16) {
return DRWAV_FALSE;
return DRWAV_EOF;
}
unsigned char sizeInBytes[8];
if (onRead(pUserData, sizeInBytes, 8) != 8) {
return DRWAV_FALSE;
return DRWAV_INVALID_FILE;
}
pHeaderOut->sizeInBytes = drwav__bytes_to_u64(sizeInBytes) - 24; // <-- Subtract 24 because w64 includes the size of the header.
......@@ -876,7 +980,7 @@ static drwav_bool32 drwav__read_chunk_header(drwav_read_proc onRead, void* pUser
*pRunningBytesReadOut += 24;
}
return DRWAV_TRUE;
return DRWAV_SUCCESS;
}
static drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData)
......@@ -899,11 +1003,38 @@ static drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 off
return DRWAV_TRUE;
}
static drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData)
{
if (offset <= 0x7FFFFFFF) {
return onSeek(pUserData, (int)offset, drwav_seek_origin_start);
}
// Larger than 32-bit seek.
if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_start)) {
return DRWAV_FALSE;
}
offset -= 0x7FFFFFFF;
for (;;) {
if (offset <= 0x7FFFFFFF) {
return onSeek(pUserData, (int)offset, drwav_seek_origin_current);
}
if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) {
return DRWAV_FALSE;
}
offset -= 0x7FFFFFFF;
}
// Should never get here.
//return DRWAV_TRUE;
}
static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_fmt* fmtOut)
{
drwav__chunk_header header;
if (!drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header)) {
drwav_chunk_header header;
if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) {
return DRWAV_FALSE;
}
......@@ -916,7 +1047,7 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe
*pRunningBytesReadOut += header.sizeInBytes + header.paddingSize;
// Try the next header.
if (!drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header)) {
if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) {
return DRWAV_FALSE;
}
}
......@@ -1041,13 +1172,18 @@ static drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek
}
drwav_bool32 drwav_init_file(drwav* pWav, const char* filename)
{
return drwav_init_file_ex(pWav, filename, NULL, NULL, 0);
}
drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags)
{
FILE* pFile = drwav_fopen(filename, "rb");
if (pFile == NULL) {
return DRWAV_FALSE;
}
return drwav_init(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, (void*)pFile);
return drwav_init_ex(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, onChunk, (void*)pFile, pChunkUserData, flags);
}
......@@ -1072,13 +1208,18 @@ drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename,
}
drwav* drwav_open_file(const char* filename)
{
return drwav_open_file_ex(filename, NULL, NULL, 0);
}
drwav* drwav_open_file_ex(const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags)
{
FILE* pFile = drwav_fopen(filename, "rb");
if (pFile == NULL) {
return DRWAV_FALSE;
}
drwav* pWav = drwav_open(drwav__on_read_stdio, drwav__on_seek_stdio, (void*)pFile);
drwav* pWav = drwav_open_ex(drwav__on_read_stdio, drwav__on_seek_stdio, onChunk, (void*)pFile, pChunkUserData, flags);
if (pWav == NULL) {
fclose(pFile);
return NULL;
......@@ -1232,6 +1373,11 @@ static drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drw
}
drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize)
{
return drwav_init_memory_ex(pWav, data, dataSize, NULL, NULL, 0);
}
drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags)
{
if (data == NULL || dataSize == 0) {
return DRWAV_FALSE;
......@@ -1243,7 +1389,7 @@ drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize)
memoryStream.dataSize = dataSize;
memoryStream.currentReadPos = 0;
if (!drwav_init(pWav, drwav__on_read_memory, drwav__on_seek_memory, (void*)&memoryStream)) {
if (!drwav_init_ex(pWav, drwav__on_read_memory, drwav__on_seek_memory, onChunk, (void*)&memoryStream, pChunkUserData, flags)) {
return DRWAV_FALSE;
}
......@@ -1291,6 +1437,11 @@ drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size
drwav* drwav_open_memory(const void* data, size_t dataSize)
{
return drwav_open_memory_ex(data, dataSize, NULL, NULL, 0);
}
drwav* drwav_open_memory_ex(const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags)
{
if (data == NULL || dataSize == 0) {
return NULL;
......@@ -1302,7 +1453,7 @@ drwav* drwav_open_memory(const void* data, size_t dataSize)
memoryStream.dataSize = dataSize;
memoryStream.currentReadPos = 0;
drwav* pWav = drwav_open(drwav__on_read_memory, drwav__on_seek_memory, (void*)&memoryStream);
drwav* pWav = drwav_open_ex(drwav__on_read_memory, drwav__on_seek_memory, onChunk, (void*)&memoryStream, pChunkUserData, flags);
if (pWav == NULL) {
return NULL;
}
......@@ -1351,19 +1502,58 @@ drwav* drwav_open_memory_write_sequential(void** ppData, size_t* pDataSize, cons
}
size_t drwav__on_read(drwav_read_proc onRead, void* pUserData, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor)
{
drwav_assert(onRead != NULL);
drwav_assert(pCursor != NULL);
size_t bytesRead = onRead(pUserData, pBufferOut, bytesToRead);
*pCursor += bytesRead;
return bytesRead;
}
drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserData, int offset, drwav_seek_origin origin, drwav_uint64* pCursor)
{
drwav_assert(onSeek != NULL);
drwav_assert(pCursor != NULL);
if (!onSeek(pUserData, offset, origin)) {
return DRWAV_FALSE;
}
if (origin == drwav_seek_origin_start) {
*pCursor = offset;
} else {
*pCursor += offset;
}
return DRWAV_TRUE;
}
drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData)
{
return drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0);
}
drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags)
{
if (onRead == NULL || onSeek == NULL) {
return DRWAV_FALSE;
}
drwav_zero_memory(pWav, sizeof(*pWav));
drwav_uint64 cursor = 0; // <-- Keeps track of the byte position so we can seek to specific locations.
drwav_bool32 sequential = (flags & DRWAV_SEQUENTIAL) != 0;
drwav_zero_memory(pWav, sizeof(*pWav));
pWav->onRead = onRead;
pWav->onSeek = onSeek;
pWav->pUserData = pReadSeekUserData;
// The first 4 bytes should be the RIFF identifier.
unsigned char riff[4];
if (onRead(pUserData, riff, sizeof(riff)) != sizeof(riff)) {
return DRWAV_FALSE; // Failed to read data.
if (drwav__on_read(onRead, pReadSeekUserData, riff, sizeof(riff), &cursor) != sizeof(riff)) {
return DRWAV_FALSE;
}
// The first 4 bytes can be used to identify the container. For RIFF files it will start with "RIFF" and for
......@@ -1375,7 +1565,7 @@ drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onS
// Check the rest of the GUID for validity.
drwav_uint8 riff2[12];
if (onRead(pUserData, riff2, sizeof(riff2)) != sizeof(riff2)) {
if (drwav__on_read(onRead, pReadSeekUserData, riff2, sizeof(riff2), &cursor) != sizeof(riff2)) {
return DRWAV_FALSE;
}
......@@ -1392,7 +1582,7 @@ drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onS
if (pWav->container == drwav_container_riff) {
// RIFF/WAVE
unsigned char chunkSizeBytes[4];
if (onRead(pUserData, chunkSizeBytes, sizeof(chunkSizeBytes)) != sizeof(chunkSizeBytes)) {
if (drwav__on_read(onRead, pReadSeekUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) {
return DRWAV_FALSE;
}
......@@ -1402,19 +1592,17 @@ drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onS
}
unsigned char wave[4];
if (onRead(pUserData, wave, sizeof(wave)) != sizeof(wave)) {
if (drwav__on_read(onRead, pReadSeekUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) {
return DRWAV_FALSE;
}
if (!drwav__fourcc_equal(wave, "WAVE")) {
return DRWAV_FALSE; // Expecting "WAVE".
}
pWav->dataChunkDataPos = 4 + sizeof(chunkSizeBytes) + sizeof(wave);
} else {
// W64
unsigned char chunkSize[8];
if (onRead(pUserData, chunkSize, sizeof(chunkSize)) != sizeof(chunkSize)) {
if (drwav__on_read(onRead, pReadSeekUserData, chunkSize, sizeof(chunkSize), &cursor) != sizeof(chunkSize)) {
return DRWAV_FALSE;
}
......@@ -1423,21 +1611,19 @@ drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onS
}
drwav_uint8 wave[16];
if (onRead(pUserData, wave, sizeof(wave)) != sizeof(wave)) {
if (drwav__on_read(onRead, pReadSeekUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) {
return DRWAV_FALSE;
}
if (!drwav__guid_equal(wave, drwavGUID_W64_WAVE)) {
return DRWAV_FALSE;
}
pWav->dataChunkDataPos = 16 + sizeof(chunkSize) + sizeof(wave);
}
// The next bytes should be the "fmt " chunk.
drwav_fmt fmt;
if (!drwav__read_fmt(onRead, onSeek, pUserData, pWav->container, &pWav->dataChunkDataPos, &fmt)) {
if (!drwav__read_fmt(onRead, onSeek, pReadSeekUserData, pWav->container, &cursor, &fmt)) {
return DRWAV_FALSE; // Failed to read the "fmt " chunk.
}
......@@ -1454,37 +1640,80 @@ drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onS
}
drwav_uint64 sampleCountFromFactChunk = 0;
// We need to enumerate over each chunk for two reasons:
// 1) The "data" chunk may not be the next one
// 2) We may want to report each chunk back to the client
//
// In order to correctly report each chunk back to the client we will need to keep looping until the end of the file.
drwav_bool32 foundDataChunk = DRWAV_FALSE;
drwav_uint64 dataChunkSize = 0;
// The next chunk we care about is the "data" chunk. This is not necessarily the next chunk so we'll need to loop.
drwav_uint64 dataSize;
drwav_uint64 chunkSize = 0;
for (;;)
{
drwav__chunk_header header;
if (!drwav__read_chunk_header(onRead, pUserData, pWav->container, &pWav->dataChunkDataPos, &header)) {
return DRWAV_FALSE;
drwav_chunk_header header;
drwav_result result = drwav__read_chunk_header(onRead, pReadSeekUserData, pWav->container, &cursor, &header);
if (result != DRWAV_SUCCESS) {
if (!foundDataChunk) {
return DRWAV_FALSE;
} else {
break; // Probably at the end of the file. Get out of the loop.
}
}
dataSize = header.sizeInBytes;
// Tell the client about this chunk.
if (!sequential && onChunk != NULL) {
drwav_uint64 callbackBytesRead = onChunk(pChunkUserData, onRead, onSeek, pReadSeekUserData, &header);
// dr_wav may need to read the contents of the chunk, so we now need to seek back to the position before
// we called the callback.
if (callbackBytesRead > 0) {
if (!drwav__seek_from_start(onSeek, cursor, pReadSeekUserData)) {
return DRWAV_FALSE;
}
}
}
if (!foundDataChunk) {
pWav->dataChunkDataPos = cursor;
}
chunkSize = header.sizeInBytes;
if (pWav->container == drwav_container_riff) {
if (drwav__fourcc_equal(header.id.fourcc, "data")) {
break;
foundDataChunk = DRWAV_TRUE;
dataChunkSize = chunkSize;
}
} else {
if (drwav__guid_equal(header.id.guid, drwavGUID_W64_DATA)) {
break;
foundDataChunk = DRWAV_TRUE;
dataChunkSize = chunkSize;
}
}
// If at this point we have found the data chunk and we're running in sequential mode, we need to break out of this loop. The reason for
// this is that we would otherwise require a backwards seek which sequential mode forbids.
if (foundDataChunk && sequential) {
break;
}
// Optional. Get the total sample count from the FACT chunk. This is useful for compressed formats.
if (pWav->container == drwav_container_riff) {
if (drwav__fourcc_equal(header.id.fourcc, "fact")) {
drwav_uint32 sampleCount;
if (onRead(pUserData, &sampleCount, 4) != 4) {
if (drwav__on_read(onRead, pReadSeekUserData, &sampleCount, 4, &cursor) != 4) {
return DRWAV_FALSE;
}
pWav->dataChunkDataPos += 4;
dataSize -= 4;
chunkSize -= 4;
if (!foundDataChunk) {
pWav->dataChunkDataPos = cursor;
}
// The sample count in the "fact" chunk is either unreliable, or I'm not understanding it properly. For now I am only enabling this
// for Microsoft ADPCM formats.
......@@ -1496,52 +1725,118 @@ drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onS
}
} else {
if (drwav__guid_equal(header.id.guid, drwavGUID_W64_FACT)) {
if (onRead(pUserData, &sampleCountFromFactChunk, 8) != 8) {
if (drwav__on_read(onRead, pReadSeekUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) {
return DRWAV_FALSE;
}
pWav->dataChunkDataPos += 8;
dataSize -= 8;
chunkSize -= 8;
if (!foundDataChunk) {
pWav->dataChunkDataPos = cursor;
}
}
}
// If we get here it means we didn't find the "data" chunk. Seek past it.
// "smpl" chunk.
if (pWav->container == drwav_container_riff) {
if (drwav__fourcc_equal(header.id.fourcc, "smpl")) {
unsigned char smplHeaderData[36]; // 36 = size of the smpl header section, not including the loop data.
if (chunkSize >= sizeof(smplHeaderData)) {
drwav_uint64 bytesJustRead = drwav__on_read(onRead, pReadSeekUserData, smplHeaderData, sizeof(smplHeaderData), &cursor);
chunkSize -= bytesJustRead;
if (bytesJustRead == sizeof(smplHeaderData)) {
pWav->smpl.manufacturer = drwav__bytes_to_u32(smplHeaderData+0);
pWav->smpl.product = drwav__bytes_to_u32(smplHeaderData+4);
pWav->smpl.samplePeriod = drwav__bytes_to_u32(smplHeaderData+8);
pWav->smpl.midiUnityNotes = drwav__bytes_to_u32(smplHeaderData+12);
pWav->smpl.midiPitchFraction = drwav__bytes_to_u32(smplHeaderData+16);
pWav->smpl.smpteFormat = drwav__bytes_to_u32(smplHeaderData+20);
pWav->smpl.smpteOffset = drwav__bytes_to_u32(smplHeaderData+24);
pWav->smpl.numSampleLoops = drwav__bytes_to_u32(smplHeaderData+28);
pWav->smpl.samplerData = drwav__bytes_to_u32(smplHeaderData+32);
for (drwav_uint32 iLoop = 0; iLoop < pWav->smpl.numSampleLoops && iLoop < drwav_countof(pWav->smpl.loops); ++iLoop) {
unsigned char smplLoopData[24]; // 24 = size of a loop section in the smpl chunk.
bytesJustRead = drwav__on_read(onRead, pReadSeekUserData, smplLoopData, sizeof(smplLoopData), &cursor);
chunkSize -= bytesJustRead;
if (bytesJustRead == sizeof(smplLoopData)) {
pWav->smpl.loops[iLoop].cuePointId = drwav__bytes_to_u32(smplLoopData+0);
pWav->smpl.loops[iLoop].type = drwav__bytes_to_u32(smplLoopData+4);
pWav->smpl.loops[iLoop].start = drwav__bytes_to_u32(smplLoopData+8);
pWav->smpl.loops[iLoop].end = drwav__bytes_to_u32(smplLoopData+12);
pWav->smpl.loops[iLoop].fraction = drwav__bytes_to_u32(smplLoopData+16);
pWav->smpl.loops[iLoop].playCount = drwav__bytes_to_u32(smplLoopData+20);
} else {
break; // Break from the smpl loop for loop.
}
}
}
} else {
// Looks like invalid data. Ignore the chunk.
}
}
} else {
if (drwav__guid_equal(header.id.guid, drwavGUID_W64_SMPL)) {
// This path will be hit when a W64 WAV file contains a smpl chunk. I don't have a sample file to test this path, so a contribution
// is welcome to add support for this.
}
}
// Make sure we seek past the padding.
dataSize += header.paddingSize;
drwav__seek_forward(onSeek, dataSize, pUserData);
pWav->dataChunkDataPos += dataSize;
chunkSize += header.paddingSize;
if (!drwav__seek_forward(onSeek, chunkSize, pReadSeekUserData)) {
break;
}
cursor += chunkSize;
if (!foundDataChunk) {
pWav->dataChunkDataPos = cursor;
}
}
// If we haven't found a data chunk, return an error.
if (!foundDataChunk) {
return DRWAV_FALSE;
}
// We may have moved passed the data chunk. If so we need to move back. If running in sequential mode we can assume we are already sitting on the data chunk.
if (!sequential) {
if (!drwav__seek_from_start(onSeek, pWav->dataChunkDataPos, pReadSeekUserData)) {
return DRWAV_FALSE;
}
cursor = pWav->dataChunkDataPos;
}
// At this point we should be sitting on the first byte of the raw audio data.
pWav->onRead = onRead;
pWav->onSeek = onSeek;
pWav->pUserData = pUserData;
pWav->fmt = fmt;
pWav->sampleRate = fmt.sampleRate;
pWav->channels = fmt.channels;
pWav->bitsPerSample = fmt.bitsPerSample;
pWav->bytesPerSample = fmt.blockAlign / fmt.channels;
pWav->bytesRemaining = dataSize;
pWav->bytesRemaining = dataChunkSize;
pWav->translatedFormatTag = translatedFormatTag;
pWav->dataChunkDataSize = dataSize;
pWav->dataChunkDataSize = dataChunkSize;
// 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;
// The number of bytes per sample is based on the bits per sample or the block align. We prioritize floor(bitsPerSample/8), but if
// this is zero of the bits per sample is not a multiple of 8 we need to fall back to the block align.
pWav->bytesPerSample = pWav->bitsPerSample/8;
if (pWav->bytesPerSample == 0 || (pWav->bitsPerSample & 0x7) != 0 /*|| pWav->bytesPerSample < fmt.blockAlign/fmt.channels*/) {
pWav->bytesPerSample = fmt.blockAlign/fmt.channels;
}
if (sampleCountFromFactChunk != 0) {
pWav->totalSampleCount = sampleCountFromFactChunk * fmt.channels;
} else {
pWav->totalSampleCount = dataSize / pWav->bytesPerSample;
pWav->totalSampleCount = dataChunkSize / pWav->bytesPerSample;
if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) {
drwav_uint64 blockCount = dataSize / fmt.blockAlign;
drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign;
pWav->totalSampleCount = (blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2; // x2 because two samples per byte.
}
if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) {
drwav_uint64 blockCount = dataSize / fmt.blockAlign;
drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign;
pWav->totalSampleCount = ((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels);
}
}
......@@ -1566,11 +1861,11 @@ drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onS
// 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.
if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) {
drwav_uint64 blockCount = dataSize / fmt.blockAlign;
drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign;
pWav->totalSampleCount = (blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2; // x2 because two samples per byte.
}
if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) {
drwav_uint64 blockCount = dataSize / fmt.blockAlign;
drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign;
pWav->totalSampleCount = ((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels);
}
#endif
......@@ -1819,13 +2114,18 @@ void drwav_uninit(drwav* pWav)
drwav* drwav_open(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData)
{
return drwav_open_ex(onRead, onSeek, NULL, pUserData, NULL, 0);
}
drwav* drwav_open_ex(drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags)
{
drwav* pWav = (drwav*)DRWAV_MALLOC(sizeof(*pWav));
if (pWav == NULL) {
return NULL;
}
if (!drwav_init(pWav, onRead, onSeek, pUserData)) {
if (!drwav_init_ex(pWav, onRead, onSeek, onChunk, pReadSeekUserData, pChunkUserData, flags)) {
DRWAV_FREE(pWav);
return NULL;
}
......@@ -1884,7 +2184,7 @@ size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut)
drwav_uint64 drwav_read(drwav* pWav, drwav_uint64 samplesToRead, void* pBufferOut)
{
if (pWav == NULL || samplesToRead == 0 || pBufferOut == NULL) {
if (pWav == NULL || samplesToRead == 0 || pBufferOut == NULL || pWav->bytesPerSample == 0) {
return 0;
}
......@@ -1902,6 +2202,11 @@ drwav_uint64 drwav_read(drwav* pWav, drwav_uint64 samplesToRead, void* pBufferOu
return bytesRead / pWav->bytesPerSample;
}
drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut)
{
return drwav_read(pWav, framesToRead * pWav->channels, pBufferOut) / pWav->channels;
}
drwav_bool32 drwav_seek_to_first_sample(drwav* pWav)
{
if (pWav->onWrite != NULL) {
......@@ -2015,6 +2320,11 @@ drwav_bool32 drwav_seek_to_sample(drwav* pWav, drwav_uint64 sample)
return DRWAV_TRUE;
}
drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex)
{
return drwav_seek_to_sample(pWav, targetFrameIndex * pWav->channels);
}
size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData)
{
......@@ -2060,6 +2370,11 @@ drwav_uint64 drwav_write(drwav* pWav, drwav_uint64 samplesToWrite, const void* p
return (bytesWritten * 8) / pWav->bitsPerSample;
}
drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData)
{
return drwav_write(pWav, framesToWrite * pWav->channels, pData) / pWav->channels;
}
drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut)
......@@ -2482,9 +2797,13 @@ static void drwav__ieee_to_s16(drwav_int16* pOut, const unsigned char* pIn, size
drwav_uint64 drwav_read_s16__pcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut)
{
// Fast path.
if (pWav->bytesPerSample == 2) {
if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) {
return drwav_read(pWav, samplesToRead, pBufferOut);
}
if (pWav->bytesPerSample == 0) {
return 0;
}
drwav_uint64 totalSamplesRead = 0;
unsigned char sampleData[4096];
......@@ -2506,6 +2825,10 @@ drwav_uint64 drwav_read_s16__pcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_
drwav_uint64 drwav_read_s16__ieee(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut)
{
if (pWav->bytesPerSample == 0) {
return 0;
}
drwav_uint64 totalSamplesRead = 0;
unsigned char sampleData[4096];
while (samplesToRead > 0) {
......@@ -2526,6 +2849,10 @@ drwav_uint64 drwav_read_s16__ieee(drwav* pWav, drwav_uint64 samplesToRead, drwav
drwav_uint64 drwav_read_s16__alaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut)
{
if (pWav->bytesPerSample == 0) {
return 0;
}
drwav_uint64 totalSamplesRead = 0;
unsigned char sampleData[4096];
while (samplesToRead > 0) {
......@@ -2546,6 +2873,10 @@ drwav_uint64 drwav_read_s16__alaw(drwav* pWav, drwav_uint64 samplesToRead, drwav
drwav_uint64 drwav_read_s16__mulaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut)
{
if (pWav->bytesPerSample == 0) {
return 0;
}
drwav_uint64 totalSamplesRead = 0;
unsigned char sampleData[4096];
while (samplesToRead > 0) {
......@@ -2602,6 +2933,11 @@ drwav_uint64 drwav_read_s16(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16
return 0;
}
drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut)
{
return drwav_read_s16(pWav, framesToRead * pWav->channels, pBufferOut) / pWav->channels;
}
void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount)
{
int r;
......@@ -2812,10 +3148,10 @@ drwav_uint64 drwav_read_f32__ima(drwav* pWav, drwav_uint64 samplesToRead, float*
drwav_uint64 drwav_read_f32__ieee(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut)
{
// Fast path.
if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bytesPerSample == 4) {
if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bitsPerSample == 32) {
return drwav_read(pWav, samplesToRead, pBufferOut);
}
if (pWav->bytesPerSample == 0) {
return 0;
}
......@@ -2924,6 +3260,11 @@ drwav_uint64 drwav_read_f32(drwav* pWav, drwav_uint64 samplesToRead, float* pBuf
return 0;
}
drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut)
{
return drwav_read_f32(pWav, framesToRead * pWav->channels, pBufferOut) / pWav->channels;
}
void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount)
{
if (pOut == NULL || pIn == NULL) {
......@@ -3085,10 +3426,10 @@ static void drwav__ieee_to_s32(drwav_int32* pOut, const unsigned char* pIn, size
drwav_uint64 drwav_read_s32__pcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut)
{
// Fast path.
if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bytesPerSample == 4) {
if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 32) {
return drwav_read(pWav, samplesToRead, pBufferOut);
}
if (pWav->bytesPerSample == 0) {
return 0;
}
......@@ -3266,6 +3607,11 @@ drwav_uint64 drwav_read_s32(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32
return 0;
}
drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut)
{
return drwav_read_s32(pWav, framesToRead * pWav->channels, pBufferOut) / pWav->channels;
}
void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount)
{
if (pOut == NULL || pIn == NULL) {
......@@ -3446,8 +3792,8 @@ drwav_int32* drwav__read_and_close_s32(drwav* pWav, unsigned int* channels, unsi
drwav_int16* drwav_open_and_read_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount)
{
if (sampleRate) *sampleRate = 0;
if (channels) *channels = 0;
if (sampleRate) *sampleRate = 0;
if (totalSampleCount) *totalSampleCount = 0;
drwav wav;
......@@ -3458,6 +3804,27 @@ drwav_int16* drwav_open_and_read_s16(drwav_read_proc onRead, drwav_seek_proc onS
return drwav__read_and_close_s16(&wav, channels, sampleRate, totalSampleCount);
}
drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut)
{
if (channelsOut) *channelsOut = 0;
if (sampleRateOut) *sampleRateOut = 0;
if (totalFrameCountOut) *totalFrameCountOut = 0;
unsigned int channels;
unsigned int sampleRate;
drwav_uint64 totalSampleCount;
drwav_int16* result = drwav_open_and_read_s16(onRead, onSeek, pUserData, &channels, &sampleRate, &totalSampleCount);
if (result == NULL) {
return NULL;
}
if (channelsOut) *channelsOut = channels;
if (sampleRateOut) *sampleRateOut = sampleRate;
if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels;
return result;
}
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)
{
if (sampleRate) *sampleRate = 0;
......@@ -3472,6 +3839,27 @@ float* drwav_open_and_read_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, v
return drwav__read_and_close_f32(&wav, channels, sampleRate, totalSampleCount);
}
float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut)
{
if (channelsOut) *channelsOut = 0;
if (sampleRateOut) *sampleRateOut = 0;
if (totalFrameCountOut) *totalFrameCountOut = 0;
unsigned int channels;
unsigned int sampleRate;
drwav_uint64 totalSampleCount;
float* result = drwav_open_and_read_f32(onRead, onSeek, pUserData, &channels, &sampleRate, &totalSampleCount);
if (result == NULL) {
return NULL;
}
if (channelsOut) *channelsOut = channels;
if (sampleRateOut) *sampleRateOut = sampleRate;
if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels;
return result;
}
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)
{
if (sampleRate) *sampleRate = 0;
......@@ -3486,8 +3874,29 @@ drwav_int32* drwav_open_and_read_s32(drwav_read_proc onRead, drwav_seek_proc onS
return drwav__read_and_close_s32(&wav, channels, sampleRate, totalSampleCount);
}
drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut)
{
if (channelsOut) *channelsOut = 0;
if (sampleRateOut) *sampleRateOut = 0;
if (totalFrameCountOut) *totalFrameCountOut = 0;
unsigned int channels;
unsigned int sampleRate;
drwav_uint64 totalSampleCount;
drwav_int32* result = drwav_open_and_read_s32(onRead, onSeek, pUserData, &channels, &sampleRate, &totalSampleCount);
if (result == NULL) {
return NULL;
}
if (channelsOut) *channelsOut = channels;
if (sampleRateOut) *sampleRateOut = sampleRate;
if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels;
return result;
}
#ifndef DR_WAV_NO_STDIO
drwav_int16* drwav_open_and_read_file_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount)
drwav_int16* drwav_open_file_and_read_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount)
{
if (sampleRate) *sampleRate = 0;
if (channels) *channels = 0;
......@@ -3501,7 +3910,28 @@ drwav_int16* drwav_open_and_read_file_s16(const char* filename, unsigned int* ch
return drwav__read_and_close_s16(&wav, channels, sampleRate, totalSampleCount);
}
float* drwav_open_and_read_file_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount)
drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut)
{
if (channelsOut) *channelsOut = 0;
if (sampleRateOut) *sampleRateOut = 0;
if (totalFrameCountOut) *totalFrameCountOut = 0;
unsigned int channels;
unsigned int sampleRate;
drwav_uint64 totalSampleCount;
drwav_int16* result = drwav_open_file_and_read_s16(filename, &channels, &sampleRate, &totalSampleCount);
if (result == NULL) {
return NULL;
}
if (channelsOut) *channelsOut = channels;
if (sampleRateOut) *sampleRateOut = sampleRate;
if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels;
return result;
}
float* drwav_open_file_and_read_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount)
{
if (sampleRate) *sampleRate = 0;
if (channels) *channels = 0;
......@@ -3515,7 +3945,28 @@ float* drwav_open_and_read_file_f32(const char* filename, unsigned int* channels
return drwav__read_and_close_f32(&wav, channels, sampleRate, totalSampleCount);
}
drwav_int32* drwav_open_and_read_file_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount)
float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut)
{
if (channelsOut) *channelsOut = 0;
if (sampleRateOut) *sampleRateOut = 0;
if (totalFrameCountOut) *totalFrameCountOut = 0;
unsigned int channels;
unsigned int sampleRate;
drwav_uint64 totalSampleCount;
float* result = drwav_open_file_and_read_f32(filename, &channels, &sampleRate, &totalSampleCount);
if (result == NULL) {
return NULL;
}
if (channelsOut) *channelsOut = channels;
if (sampleRateOut) *sampleRateOut = sampleRate;
if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels;
return result;
}
drwav_int32* drwav_open_file_and_read_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount)
{
if (sampleRate) *sampleRate = 0;
if (channels) *channels = 0;
......@@ -3528,9 +3979,30 @@ drwav_int32* drwav_open_and_read_file_s32(const char* filename, unsigned int* ch
return drwav__read_and_close_s32(&wav, channels, sampleRate, totalSampleCount);
}
drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut)
{
if (channelsOut) *channelsOut = 0;
if (sampleRateOut) *sampleRateOut = 0;
if (totalFrameCountOut) *totalFrameCountOut = 0;
unsigned int channels;
unsigned int sampleRate;
drwav_uint64 totalSampleCount;
drwav_int32* result = drwav_open_file_and_read_s32(filename, &channels, &sampleRate, &totalSampleCount);
if (result == NULL) {
return NULL;
}
if (channelsOut) *channelsOut = channels;
if (sampleRateOut) *sampleRateOut = sampleRate;
if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels;
return result;
}
#endif
drwav_int16* drwav_open_and_read_memory_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount)
drwav_int16* drwav_open_memory_and_read_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount)
{
if (sampleRate) *sampleRate = 0;
if (channels) *channels = 0;
......@@ -3544,7 +4016,28 @@ drwav_int16* drwav_open_and_read_memory_s16(const void* data, size_t dataSize, u
return drwav__read_and_close_s16(&wav, channels, sampleRate, totalSampleCount);
}
float* drwav_open_and_read_memory_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount)
drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut)
{
if (channelsOut) *channelsOut = 0;
if (sampleRateOut) *sampleRateOut = 0;
if (totalFrameCountOut) *totalFrameCountOut = 0;
unsigned int channels;
unsigned int sampleRate;
drwav_uint64 totalSampleCount;
drwav_int16* result = drwav_open_memory_and_read_s16(data, dataSize, &channels, &sampleRate, &totalSampleCount);
if (result == NULL) {
return NULL;
}
if (channelsOut) *channelsOut = channels;
if (sampleRateOut) *sampleRateOut = sampleRate;
if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels;
return result;
}
float* drwav_open_memory_and_read_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount)
{
if (sampleRate) *sampleRate = 0;
if (channels) *channels = 0;
......@@ -3558,7 +4051,28 @@ float* drwav_open_and_read_memory_f32(const void* data, size_t dataSize, unsigne
return drwav__read_and_close_f32(&wav, channels, sampleRate, totalSampleCount);
}
drwav_int32* drwav_open_and_read_memory_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount)
float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut)
{
if (channelsOut) *channelsOut = 0;
if (sampleRateOut) *sampleRateOut = 0;
if (totalFrameCountOut) *totalFrameCountOut = 0;
unsigned int channels;
unsigned int sampleRate;
drwav_uint64 totalSampleCount;
float* result = drwav_open_memory_and_read_f32(data, dataSize, &channels, &sampleRate, &totalSampleCount);
if (result == NULL) {
return NULL;
}
if (channelsOut) *channelsOut = channels;
if (sampleRateOut) *sampleRateOut = sampleRate;
if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels;
return result;
}
drwav_int32* drwav_open_memory_and_read_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount)
{
if (sampleRate) *sampleRate = 0;
if (channels) *channels = 0;
......@@ -3571,6 +4085,27 @@ drwav_int32* drwav_open_and_read_memory_s32(const void* data, size_t dataSize, u
return drwav__read_and_close_s32(&wav, channels, sampleRate, totalSampleCount);
}
drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut)
{
if (channelsOut) *channelsOut = 0;
if (sampleRateOut) *sampleRateOut = 0;
if (totalFrameCountOut) *totalFrameCountOut = 0;
unsigned int channels;
unsigned int sampleRate;
drwav_uint64 totalSampleCount;
drwav_int32* result = drwav_open_memory_and_read_s32(data, dataSize, &channels, &sampleRate, &totalSampleCount);
if (result == NULL) {
return NULL;
}
if (channelsOut) *channelsOut = channels;
if (sampleRateOut) *sampleRateOut = sampleRate;
if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels;
return result;
}
#endif //DR_WAV_NO_CONVERSION_API
......@@ -3584,6 +4119,15 @@ void drwav_free(void* pDataReturnedByOpenAndRead)
// REVISION HISTORY
//
// v0.9.0 - 2018-xx-xx
// - API CHANGE: Rename drwav_open_and_read_file_*() to drwav_open_file_and_read_*().
// - API CHANGE: Rename drwav_open_and_read_memory_*() to drwav_open_memory_and_read_*().
// - Add built-in support for smpl chunks.
// - Add support for firing a callback for each chunk in the file at initialization time.
// - This is enabled through the drwav_init_ex(), etc. family of APIs.
// - Add new reading APIs for reading by PCM frames instead of samples.
// - Handle invalid FMT chunks more robustly.
//
// v0.8.5 - 2018-09-11
// - Const correctness.
// - Fix a potential stack overflow.
......
// Audio playback and capture library. Public domain. See "unlicense" statement at the end of this file.
// mini_al - v0.8.13 - 2018-12-04
// mini_al - v0.8.14 - 2018-12-16
//
// David Reid - davidreidsoftware@gmail.com
......@@ -18247,10 +18247,16 @@ void mal_device_uninit__opensl(mal_device* pDevice)
// Uninit device.
if (pDevice->type == mal_device_type_playback) {
if (pDevice->opensl.pAudioPlayerObj) MAL_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->Destroy((SLObjectItf)pDevice->opensl.pAudioPlayerObj);
if (pDevice->opensl.pOutputMixObj) MAL_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->Destroy((SLObjectItf)pDevice->opensl.pOutputMixObj);
if (pDevice->opensl.pAudioPlayerObj) {
MAL_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->Destroy((SLObjectItf)pDevice->opensl.pAudioPlayerObj);
}
if (pDevice->opensl.pOutputMixObj) {
MAL_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->Destroy((SLObjectItf)pDevice->opensl.pOutputMixObj);
}
} else {
if (pDevice->opensl.pAudioRecorderObj) MAL_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->Destroy((SLObjectItf)pDevice->opensl.pAudioRecorderObj);
if (pDevice->opensl.pAudioRecorderObj) {
MAL_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->Destroy((SLObjectItf)pDevice->opensl.pAudioRecorderObj);
}
}
mal_free(pDevice->opensl.pBuffer);
......@@ -27083,7 +27089,7 @@ mal_result mal_decoder_internal_on_seek_to_frame__wav(mal_decoder* pDecoder, mal
drwav* pWav = (drwav*)pDecoder->pInternalDecoder;
mal_assert(pWav != NULL);
drwav_bool32 result = drwav_seek_to_sample(pWav, frameIndex*pWav->channels);
drwav_bool32 result = drwav_seek_to_pcm_frame(pWav, frameIndex);
if (result) {
return MAL_SUCCESS;
} else {
......@@ -27108,9 +27114,9 @@ mal_uint32 mal_decoder_internal_on_read_frames__wav(mal_dsp* pDSP, mal_uint32 fr
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;
case mal_format_s16: return (mal_uint32)drwav_read_pcm_frames_s16(pWav, frameCount, (drwav_int16*)pSamplesOut);
case mal_format_s32: return (mal_uint32)drwav_read_pcm_frames_s32(pWav, frameCount, (drwav_int32*)pSamplesOut);
case mal_format_f32: return (mal_uint32)drwav_read_pcm_frames_f32(pWav, frameCount, (float*)pSamplesOut);
default: break;
}
......@@ -27210,7 +27216,7 @@ mal_result mal_decoder_internal_on_seek_to_frame__flac(mal_decoder* pDecoder, ma
drflac* pFlac = (drflac*)pDecoder->pInternalDecoder;
mal_assert(pFlac != NULL);
drflac_bool32 result = drflac_seek_to_sample(pFlac, frameIndex*pFlac->channels);
drflac_bool32 result = drflac_seek_to_pcm_frame(pFlac, frameIndex);
if (result) {
return MAL_SUCCESS;
} else {
......@@ -27235,7 +27241,7 @@ mal_uint32 mal_decoder_internal_on_read_frames__flac(mal_dsp* pDSP, mal_uint32 f
drflac* pFlac = (drflac*)pDecoder->pInternalDecoder;
mal_assert(pFlac != NULL);
return (mal_uint32)drflac_read_s32(pFlac, frameCount*pDecoder->internalChannels, (drflac_int32*)pSamplesOut) / pDecoder->internalChannels;
return (mal_uint32)drflac_read_pcm_frames_s32(pFlac, frameCount, (drflac_int32*)pSamplesOut);
}
mal_result mal_decoder_init_flac__internal(const mal_decoder_config* pConfig, mal_decoder* pDecoder)
......@@ -27574,7 +27580,7 @@ mal_result mal_decoder_internal_on_seek_to_frame__mp3(mal_decoder* pDecoder, mal
drmp3* pMP3 = (drmp3*)pDecoder->pInternalDecoder;
mal_assert(pMP3 != NULL);
drmp3_bool32 result = drmp3_seek_to_frame(pMP3, frameIndex);
drmp3_bool32 result = drmp3_seek_to_pcm_frame(pMP3, frameIndex);
if (result) {
return MAL_SUCCESS;
} else {
......@@ -27600,7 +27606,7 @@ mal_uint32 mal_decoder_internal_on_read_frames__mp3(mal_dsp* pDSP, mal_uint32 fr
drmp3* pMP3 = (drmp3*)pDecoder->pInternalDecoder;
mal_assert(pMP3 != NULL);
return (mal_uint32)drmp3_read_f32(pMP3, frameCount, (float*)pSamplesOut);
return (mal_uint32)drmp3_read_pcm_frames_f32(pMP3, frameCount, (float*)pSamplesOut);
}
mal_result mal_decoder_init_mp3__internal(const mal_decoder_config* pConfig, mal_decoder* pDecoder)
......@@ -28484,6 +28490,10 @@ mal_uint64 mal_sine_wave_read_ex(mal_sine_wave* pSineWave, mal_uint64 frameCount
// REVISION HISTORY
// ================
//
// v0.8.14 - 2018-12-16
// - Core Audio: Fix a bug where the device state is not set correctly after stopping.
// - Update decoders to use updated APIs in dr_flac, dr_mp3 and dr_wav.
//
// v0.8.13 - 2018-12-04
// - Core Audio: Fix a bug with channel mapping.
// - Fix a bug with channel routing where the back/left and back/right channels have the wrong weight.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment