dr_libs icon indicating copy to clipboard operation
dr_libs copied to clipboard

dr_wav append write?

Open Youlean opened this issue 2 years ago • 8 comments

Hi, is there a way for dr_wav to append data to a wav file when writing instead of rewriting the file?

Youlean avatar Oct 16 '21 13:10 Youlean

No, unfortunately appending is not supported. The writing API was only really designed for basic use cases.

mackron avatar Oct 17 '21 00:10 mackron

Thanks. It might be complex to implement it to dr_wav. I have looked and I will have to have a deep understanding of the lib to be able to implement it.

The way to do it:

open file seek to the end of the file write new data read old wav header create a new wav header and overwrite the old one

This lib is doing exactly that: https://github.com/justinfrankel/WDL/blob/master/WDL/wavwrite.h

Youlean avatar Oct 17 '21 15:10 Youlean

Yeah I'm not against adding support or anything. I'll mark this as a feature request and get to it when I can, but it could be a ways away.

mackron avatar Oct 17 '21 20:10 mackron

I qm already working on it. Might have it ready in a couple of hours

Youlean avatar Oct 17 '21 20:10 Youlean

I think I got it. I will need to do more testing, but so far it works great.

Here are the changes/additions:

typedef enum
{
    drwav_seek_origin_start,
    drwav_seek_origin_current,
    drwav_seek_origin_end
} drwav_seek_origin;
DRWAV_PRIVATE drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek_origin origin)
{
  drwav_uint32 seekOrigin = SEEK_SET;
  
  if (origin == drwav_seek_origin_current)
    seekOrigin = SEEK_CUR;
  
  if (origin == drwav_seek_origin_end)
    seekOrigin = SEEK_END;
  
    return fseek((FILE*)pUserData, offset, seekOrigin) == 0;
}
DRWAV_API drwav_bool32 drwav_init_file_write_append(drwav* pWav, const char* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks)
{
  return drwav_init_file_write_append__internal(pWav, filename, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks);
}
DRWAV_PRIVATE drwav_bool32 drwav_init_file_write_append__internal(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks)
{
  /* Open the file and get current data chunk size */
  drwav_uint64 dataChunkDataSize = 0;
  drwav_uint64 dataChunkDataPos = 0;
  
  drwav_bool32 appendToFile = DRWAV_FALSE;
  
  /* Open file for writing */
  FILE* pFile;
  if (drwav_fopen(&pFile, filename, "r+b") == DRWAV_SUCCESS)
  {
    if (drwav_init_file(pWav, filename, NULL)) {
      dataChunkDataSize = pWav->dataChunkDataSize;
      dataChunkDataPos = pWav->dataChunkDataPos;
      
      appendToFile = DRWAV_TRUE;
    }
    
    drwav_uninit(pWav);
        
    drwav_bool32 result = drwav_preinit_write(pWav, pFormat, isSequential, drwav__on_write_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks);
    
    if (result != DRWAV_TRUE) {
      fclose(pFile);
      return result;
    }
    
    pWav->container = pFormat->container;
    pWav->channels = (drwav_uint16)pFormat->channels;
    pWav->sampleRate = pFormat->sampleRate;
    pWav->bitsPerSample = (drwav_uint16)pFormat->bitsPerSample;
    pWav->translatedFormatTag = (drwav_uint16)pFormat->format;
    
    if (appendToFile)
    {
      pWav->dataChunkDataSize = dataChunkDataSize;
      pWav->dataChunkDataPos = dataChunkDataPos;
      
      if (pWav->onSeek) {
        pWav->onSeek(pWav->pUserData, 0, drwav_seek_origin::drwav_seek_origin_end);
      }
    }
    
    return result;
  }
  else if (drwav_fopen(&pFile, filename, "wb") != DRWAV_SUCCESS)
  {
    return DRWAV_FALSE;
  }
  
  /* This takes ownership of the FILE* object. */
  return drwav_init_file_write__internal_FILE(pWav, pFile, pFormat, totalSampleCount, isSequential, pAllocationCallbacks);
}

Youlean avatar Oct 17 '21 20:10 Youlean

drwav_init_file_write_append__internal was made a bit more complicated but in this way falling to call drwav_uninit won't render the file unreadable.

That basically means, if you create and write 3 seconds successfully, then close the file and open it again to append audio to it, but for some reason app crashes while writing data, and you don't call drwav_uninit (header data is not updated), the file will be still readable, and you will be able to play the first 3 seconds just fine. That is a pretty super feature to have. :)

Youlean avatar Oct 17 '21 20:10 Youlean

I have run some tests today, and it seems it's working as expected.

Youlean avatar Oct 18 '21 11:10 Youlean

Thanks for that sample code. I'll review this when I get a chance.

mackron avatar Oct 22 '21 21:10 mackron