Skip to content

File WavWriter.h

File List > external-docs > libDaisy > src > util > WavWriter.h

Go to the documentation of this file

Source Code

#pragma once
#pragma once
#include "fatfs.h"

namespace daisy
{
template <size_t transfer_size>
class WavWriter
{
  public:
    WavWriter() {}
    ~WavWriter() {}

    enum class Result
    {
        OK,
        ERROR,
    };

    struct Config
    {
        float   samplerate;
        int32_t channels;
        int32_t bitspersample;
    };

    enum class BufferState
    {
        IDLE,
        FLUSH0,
        FLUSH1,
    };

    void Init(const Config &cfg)
    {
        cfg_       = cfg;
        num_samps_ = 0;
        // Prep the wav header according to config.
        // Certain things (i.e. Size, etc. will have to wait until the finalization of the file, or be updated while streaming).
        wavheader_.ChunkId       = kWavFileChunkId;     
        wavheader_.FileFormat    = kWavFileWaveId;      
        wavheader_.SubChunk1ID   = kWavFileSubChunk1Id; 
        wavheader_.SubChunk1Size = 16;                  // for PCM
        wavheader_.AudioFormat   = WAVE_FORMAT_PCM;
        wavheader_.NbrChannels   = cfg.channels;
        wavheader_.SampleRate    = static_cast<int>(cfg.samplerate);
        wavheader_.ByteRate      = CalcByteRate();
        wavheader_.BlockAlign    = cfg_.channels * cfg_.bitspersample / 8;
        wavheader_.BitPerSample  = cfg_.bitspersample;
        wavheader_.SubChunk2ID   = kWavFileSubChunk2Id; 
        wavheader_.FileSize = CalcFileSize();
        // This is calculated as part of the subchunk size
    }

    void Sample(const float *in)
    {
        for(int i = 0; i < cfg_.channels; i++)
        {
            switch(cfg_.bitspersample)
            {
                case 16:
                {
                    int16_t *tp;
                    tp            = (int16_t *)transfer_buff;
                    tp[wptr_ + i] = f2s16(in[i]);
                }
                break;
                case 32: transfer_buff[wptr_ + i] = f2s32(in[i]); break;
                default: break;
            }
        }
        num_samps_++;
        wptr_ += cfg_.channels;
        size_t cap_point
            = cfg_.bitspersample == 16 ? kTransferSamps * 2 : kTransferSamps;
        if(wptr_ == cap_point)
        {
            bstate_ = BufferState::FLUSH0;
        }
        if(wptr_ >= cap_point * 2)
        {
            wptr_   = 0;
            bstate_ = BufferState::FLUSH1;
        }
    }

    void Write()
    {
        if(bstate_ != BufferState::IDLE && IsRecording())
        {
            uint32_t     offset;
            unsigned int bw = 0;
            //offset          = bstate_ == BufferState::FLUSH0 ? 0 : transfer_size;
            offset  = bstate_ == BufferState::FLUSH0 ? 0 : kTransferSamps;
            bstate_ = BufferState::IDLE;
            f_write(&fp_, &transfer_buff[offset], transfer_size, &bw);
        }
    }

    void SaveFile()
    {
        unsigned int bw = 0;
        recording_      = false;

        // Flush remaining data in the transfer buffer
        if(wptr_ > 0) // Check if there is unwritten data in the buffer
        {
            uint32_t remaining_size = wptr_ * (cfg_.bitspersample / 8);
            // Ensure remaining_size does not exceed the buffer size
            if(remaining_size > sizeof(transfer_buff))
            {
                remaining_size = sizeof(transfer_buff);
            }
            f_write(&fp_, transfer_buff, remaining_size, &bw);
        }

        wavheader_.FileSize = CalcFileSize();
        f_lseek(&fp_, 0);
        f_write(&fp_, &wavheader_, sizeof(wavheader_), &bw);
        f_close(&fp_);

        // Clear the transfer buffer and reset the buffer state
        memset(transfer_buff, 0, sizeof(transfer_buff));
        bstate_    = BufferState::IDLE;
        wptr_      = 0;     // Reset the write pointer
        num_samps_ = 0;     // Reset the number of samples
        recording_ = false; // Ensure recording is inactive
    }

    void OpenFile(const char *name)
    {
        if(f_open(&fp_, name, FA_WRITE | FA_CREATE_ALWAYS) == FR_OK)
        {
            unsigned int bw = 0;
            if(f_write(&fp_, &wavheader_, sizeof(wavheader_), &bw) == FR_OK)
            {
                recording_ = true;
                num_samps_ = 0;
            }
        }
    }

    inline bool IsRecording() const { return recording_; }

    inline uint32_t GetLengthSamps() { return num_samps_; }

    inline float GetLengthSeconds()
    {
        return (float)num_samps_ / (float)cfg_.samplerate;
    }

  private:
    inline uint32_t CalcFileSize()
    {
        wavheader_.SubCHunk2Size
            = num_samps_ * cfg_.channels * cfg_.bitspersample / 8;
        return 36 + wavheader_.SubCHunk2Size;
    }

    inline uint32_t CalcByteRate()
    {
        return cfg_.samplerate * cfg_.channels * cfg_.bitspersample / 8;
    }

    static constexpr int kTransferSamps = transfer_size / sizeof(int32_t);

    WAV_FormatTypeDef wavheader_;
    uint32_t          num_samps_, wptr_;
    Config            cfg_;
    int32_t           transfer_buff[kTransferSamps * 2];
    BufferState       bstate_;
    bool              recording_;
    FIL               fp_;
};

} // namespace daisy