// SPDX-License-Identifier: Unlicense

#include "pcm.h"

#include "ogg.h"
#include "helper.h"

#ifdef SHR3D_AUDIO_SDL
#include <SDL.h>
#endif // SHR3D_AUDIO_SDL

#ifdef SHR3D_AUDIO_WASAPI
#include <mfapi.h>
#include <mftransform.h>
#include <Wmcodecdsp.h>
#include <Mferror.h>

#pragma comment(lib, "Mfplat.lib")
#pragma comment(lib, "mfuuid.lib")
#pragma comment(lib, "wmcodecdspuuid")
#endif // SHR3D_AUDIO_WASAPI


//#ifdef RING_BUFFER
//static std::mutex ringBufferMutex;
//Pcm::RingBuffer::RingBuffer(u32 bufferSize)
//  : dataBuffer(reinterpret_cast<f32*>(malloc(bufferSize * sizeof(f32))))
//  , dataBufferSize(bufferSize)
//{
//}
//
//bool Pcm::RingBuffer::write(const f32* const data, const u32 dataSize)
//{
//  ASSERT(dataSize <= dataBufferSize);
//
//  if (writeIdx + dataSize > dataBufferSize)
//  {
//    const u32 atBegin = ((writeIdx + dataSize - 1) % dataBufferSize) + 1;
//    if (readIdx < atBegin)
//      return false;
//  }
//
//  const u32 writeIdxMod = writeIdx % dataBufferSize;
//
//  const u32 remainingSizeInBuffer = dataBufferSize - writeIdxMod;
//  if (dataSize <= remainingSizeInBuffer)
//  {
//    memcpy(&dataBuffer[writeIdxMod], data, dataSize * sizeof(f32));
//    writeIdx += dataSize;
//  }
//  else
//  {
//    memcpy(&dataBuffer[writeIdxMod], data, remainingSizeInBuffer * sizeof(f32));
//    memcpy(dataBuffer, &data[remainingSizeInBuffer], (dataSize - remainingSizeInBuffer) * sizeof(f32));
//    writeIdx += dataSize;
//  }
//
//  return true;
//}
//
//bool Pcm::RingBuffer::read(f32* const data, const u32 dataSize)
//{
//  ASSERT(dataSize <= dataBufferSize);
//
//  if (writeIdx == 0)
//    return false;
//
//  if (readIdx < writeIdx && readIdx + dataSize > writeIdx)
//    return false;
//
//  const u32 remainingSizeInBuffer = dataBufferSize - readIdx;
//  if (dataSize <= remainingSizeInBuffer)
//  {
//    memcpy(data, &dataBuffer[readIdx], dataSize * sizeof(f32));
//    readIdx += dataSize;
//  }
//  else
//  {
//    const u32 readIdxMod = readIdx % dataBufferSize;
//    memcpy(data, &dataBuffer[readIdx], remainingSizeInBuffer * sizeof(f32));
//    memcpy(&data[remainingSizeInBuffer], dataBuffer, (dataSize - remainingSizeInBuffer) * sizeof(f32));
//    readIdx += dataSize;
//  }
//
//  if (readIdx >= dataBufferSize)
//  {
//    readIdx = readIdx % dataBufferSize;
//    writeIdx = writeIdx % dataBufferSize;
//  }
//
//  return true;
//}
//
//Pcm::StereoRingBuffer::StereoRingBuffer(const u32 dataBufferSize)
//  : monoChannelSize(dataBufferSize)
//{
//  dataBuffer[0] = reinterpret_cast<f32*>(malloc(dataBufferSize * 2 * sizeof(f32)));
//  dataBuffer[1] = dataBuffer[0] + dataBufferSize;
//}
//
//bool Pcm::StereoRingBuffer::write(const f32* const data[2], const u32 dataSize)
//{
//  std::lock_guard<std::mutex> lock(ringBufferMutex);
//
//  ASSERT(dataSize <= monoChannelSize);
//
//  if (writeIdx + dataSize > monoChannelSize)
//  {
//    const u32 atBegin = ((writeIdx + dataSize - 1) % monoChannelSize) + 1;
//    if (readIdx < atBegin)
//      return false;
//  }
//
//  const u32 writeIdxMod = writeIdx % monoChannelSize;
//
//  const u32 remainingSizeInBuffer = monoChannelSize - writeIdxMod;
//  if (dataSize <= remainingSizeInBuffer)
//  {
//    memcpy(&dataBuffer[0][writeIdxMod], data[0], dataSize * sizeof(f32));
//    memcpy(&dataBuffer[1][writeIdxMod], data[1], dataSize * sizeof(f32));
//    writeIdx += dataSize;
//  }
//  else
//  {
//    memcpy(&dataBuffer[0][writeIdxMod], data[0], remainingSizeInBuffer * sizeof(f32));
//    memcpy(&dataBuffer[1][writeIdxMod], data[1], remainingSizeInBuffer * sizeof(f32));
//    memcpy(dataBuffer[0], &data[0][remainingSizeInBuffer], (dataSize - remainingSizeInBuffer) * sizeof(f32));
//    memcpy(dataBuffer[1], &data[1][remainingSizeInBuffer], (dataSize - remainingSizeInBuffer) * sizeof(f32));
//    writeIdx += dataSize;
//  }
//
//  return true;
//}
//
//bool Pcm::StereoRingBuffer::read(f32* const data[2], const u32 dataSize)
//{
//  std::lock_guard<std::mutex> lock(ringBufferMutex);
//
//  ASSERT(dataSize <= monoChannelSize);
//
//  if (readIdx == writeIdx)
//    return false;
//
//  if (readIdx < writeIdx && readIdx + dataSize > writeIdx)
//    return false;
//
//  const u32 remainingSizeInBuffer = monoChannelSize - readIdx;
//  if (dataSize <= remainingSizeInBuffer)
//  {
//    memcpy(data[0], &dataBuffer[0][readIdx], dataSize * sizeof(f32));
//    memcpy(data[1], &dataBuffer[1][readIdx], dataSize * sizeof(f32));
//    readIdx += dataSize;
//  }
//  else
//  {
//    const u32 readIdxMod = readIdx % monoChannelSize;
//    memcpy(data[0], &dataBuffer[0][readIdx], remainingSizeInBuffer * sizeof(f32));
//    memcpy(data[1], &dataBuffer[1][readIdx], remainingSizeInBuffer * sizeof(f32));
//    memcpy(&data[0][remainingSizeInBuffer], dataBuffer[0], (dataSize - remainingSizeInBuffer) * sizeof(f32));
//    memcpy(&data[1][remainingSizeInBuffer], dataBuffer[1], (dataSize - remainingSizeInBuffer) * sizeof(f32));
//    readIdx += dataSize;
//  }
//
//  if (readIdx >= monoChannelSize)
//  {
//    readIdx = readIdx % monoChannelSize;
//    writeIdx = writeIdx % monoChannelSize;
//  }
//
//  return true;
//}
//#endif // RING_BUFFER

i32 Pcm::decodeOgg(const u8* oggData, u64 oggDataSize, f32*& pcmData, i64& pcmMonoLength)
{
  ASSERT(oggData != nullptr);
  ASSERT(oggDataSize > 0);

  f32* tmp = pcmData;
  pcmData = nullptr;
  if (tmp)
    free(tmp);

  Ogg::vorbis* vorbis = Ogg::open(oggData, i32(oggDataSize * sizeof(f32)));

  Ogg::Info oggInfo = Ogg::getInfo(vorbis);
  ASSERT(oggInfo.channels == 2);

  const i32 samples = Ogg::decodeMemory(vorbis, (f32**)&pcmData);
  pcmMonoLength = samples;

  Ogg::close(vorbis);

  return oggInfo.sample_rate;
}

void Pcm::resample(f32*& pcmData, i64& pcmMonoLength, const i32 inSampleRate, const i32 outSampleRate)
{
#ifdef SHR3D_AUDIO_SDL

#ifdef PLATFORM_LINUX // this code seems to work on linux, but not on windows
  SDL_AudioStream* stream = SDL_NewAudioStream(AUDIO_F32LSB, 2, inSampleRate, AUDIO_F32LSB, 2, outSampleRate);
  ASSERT(stream != nullptr);

  const i32 success = SDL_AudioStreamPut(stream, pcmData, pcmMonoLength * 2 * sizeof(f32));
  ASSERT(success == 0);
  SDL_AudioStreamFlush(stream);

  const u64 roughEstimateSize = pcmMonoLength * 2 * (f32(outSampleRate) / f32(inSampleRate)) + 100;
  std::vector<f32> resampledPCM(roughEstimateSize);

  const i32 gottenBytes = SDL_AudioStreamGet(stream, resampledPCM.data(), resampledPCM.size() * sizeof(f32));

  SDL_FreeAudioStream(stream);

  ASSERT(gottenBytes % sizeof(f32) == 0);

  pcmMonoLength = gottenBytes / sizeof(f32) / 2;
  ASSERT(pcmMonoLength * 2 < roughEstimateSize);

  pcmData = reinterpret_cast<f32*>(realloc(pcmData, gottenBytes));
  ASSERT(pcmData != nullptr);

  memcpy(pcmData, resampledPCM.data(), gottenBytes);
#endif // PLATFORM_LINUX
#ifdef PLATFORM_WINDOWS // this code works on windows but glitches on linux
  SDL_AudioCVT cvt{};
  if (SDL_BuildAudioCVT(&cvt, AUDIO_F32LSB, 2, inSampleRate, AUDIO_F32LSB, 2, outSampleRate) == 1)
  {
    cvt.buf = reinterpret_cast<u8*>(malloc(pcmMonoLength * 2 * sizeof(f32) * cvt.len_mult));
    cvt.len = i32(pcmMonoLength * 2 * sizeof(f32));
    memcpy(cvt.buf, pcmData, pcmMonoLength * 2 * sizeof(f32));

    i32 result = SDL_ConvertAudio(&cvt);
    ASSERT(0 == result);

    pcmData = reinterpret_cast<f32*>(realloc(pcmData, cvt.len * cvt.len_mult));
    memcpy(pcmData, cvt.buf, cvt.len * cvt.len_mult);
    pcmMonoLength = i64(cvt.len * cvt.len_ratio) / sizeof(f32) / 2;
    free(cvt.buf);
  }
#endif // PLATFORM_WINDOWS

#else // SHR3D_AUDIO_SDL

#ifdef PLATFORM_WINDOWS
#if 1
#define CHUNK_SIZE 1024 * 1024

  u64 pcmRemainingBytes = pcmMonoLength * 2 * sizeof(f32);
  const u64 roughEstimateSize = u64(f32(pcmRemainingBytes) * (f32(outSampleRate) / f32(inSampleRate))) + 32;
  u8* outPcmData = reinterpret_cast<u8*>(malloc(roughEstimateSize));

  static IMFTransform* transform;
  static IMFMediaType* inputMediaType;
  static IMFMediaType* outputMediaType;
  HRESULT hr;
  if (transform == nullptr)
  {
    hr = CoInitialize(nullptr);
    ASSERT(hr == S_OK || hr == S_FALSE /*was already initialized*/);

    hr = MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET);
    ASSERT(hr == S_OK);

    IUnknown* transformUnk;
    hr = CoCreateInstance(CLSID_CResamplerMediaObject, nullptr, CLSCTX_INPROC_SERVER, IID_IUnknown, reinterpret_cast<void**>(&transformUnk));
    ASSERT(hr == S_OK);
    hr = transformUnk->QueryInterface(IID_PPV_ARGS(&transform));
    ASSERT(hr == S_OK);

    hr = MFCreateMediaType(&inputMediaType);
    ASSERT(hr == S_OK);
    hr = inputMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
    ASSERT(hr == S_OK);
    hr = inputMediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_Float);
    ASSERT(hr == S_OK);
    hr = inputMediaType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, 2);
    ASSERT(hr == S_OK);
    hr = inputMediaType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, sizeof(f32) * 2);
    ASSERT(hr == S_OK);
    hr = inputMediaType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 32);
    ASSERT(hr == S_OK);
    hr = inputMediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE);
    ASSERT(hr == S_OK);

    hr = MFCreateMediaType(&outputMediaType);
    ASSERT(hr == S_OK);
    hr = outputMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
    ASSERT(hr == S_OK);
    hr = outputMediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_Float);
    ASSERT(hr == S_OK);
    hr = outputMediaType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, 2);
    ASSERT(hr == S_OK);
    hr = outputMediaType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, sizeof(f32) * 2);
    ASSERT(hr == S_OK);
    hr = outputMediaType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 32);
    ASSERT(hr == S_OK);
    hr = outputMediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE);
    ASSERT(hr == S_OK);

    { // set resample quality
      IWMResamplerProps* resamplerProps;
      hr = transform->QueryInterface(IID_PPV_ARGS(&resamplerProps));
      ASSERT(hr == S_OK);
      hr = resamplerProps->SetHalfFilterLength(60); // 1 - 60
      ASSERT(hr == S_OK);
      resamplerProps->Release();
    }
  }

  hr = inputMediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, inSampleRate);
  ASSERT(hr == S_OK);
  hr = inputMediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, sizeof(f32) * 2 * inSampleRate);
  ASSERT(hr == S_OK);
  hr = transform->SetInputType(0, inputMediaType, 0);
  ASSERT(hr == S_OK);

  hr = outputMediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, outSampleRate);
  ASSERT(hr == S_OK);
  hr = outputMediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, sizeof(f32) * 2 * outSampleRate);
  ASSERT(hr == S_OK);
  hr = transform->SetOutputType(0, outputMediaType, 0);
  ASSERT(hr == S_OK);

  hr = transform->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
  ASSERT(hr == S_OK);
  hr = transform->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
  ASSERT(hr == S_OK);
  hr = transform->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
  ASSERT(hr == S_OK);

  const u32 inputBufferSize = CHUNK_SIZE * 2 * sizeof(f32);

  u32 readBytes = 0;
  u32 inputBytesProcessed = 0;
  u32 outputBytesProcessed = 0;

  for (;;)
  {
    readBytes = inputBufferSize;
    if (pcmRemainingBytes < readBytes) {
      readBytes = u32(pcmRemainingBytes);
    }
    pcmRemainingBytes -= readBytes;

    DWORD cbOutputBytes = (DWORD)((int64_t)readBytes * outSampleRate * 2 * sizeof(f32) / (inSampleRate * 2 * sizeof(f32)));
    cbOutputBytes = (cbOutputBytes + (2 * sizeof(f32) - 1)) / (2 * sizeof(f32)) * 2 * sizeof(f32);
    cbOutputBytes += 16 * 2 * sizeof(f32);

    {
      IMFMediaBuffer* spBuffer = nullptr;
      hr = MFCreateMemoryBuffer(readBytes, &spBuffer);
      ASSERT(hr == S_OK);
      BYTE* pByteBufferTo = nullptr;
      hr = spBuffer->Lock(&pByteBufferTo, nullptr, nullptr);
      ASSERT(hr == S_OK);
      memcpy(pByteBufferTo, &reinterpret_cast<u8*>(pcmData)[inputBytesProcessed], readBytes);
      inputBytesProcessed += readBytes;
      pByteBufferTo = nullptr;
      hr = spBuffer->Unlock();
      ASSERT(hr == S_OK);
      hr = spBuffer->SetCurrentLength(readBytes);
      ASSERT(hr == S_OK);
      IMFSample* pSample = nullptr;
      hr = MFCreateSample(&pSample);
      ASSERT(hr == S_OK);
      hr = pSample->AddBuffer(spBuffer);
      ASSERT(hr == S_OK);
      DWORD dwStatus;
      hr = transform->GetInputStatus(0, &dwStatus);
      ASSERT(hr == S_OK);
      ASSERT(MFT_INPUT_STATUS_ACCEPT_DATA == dwStatus); // data not accepted

      hr = transform->ProcessInput(0, pSample, 0);
      ASSERT(hr == S_OK);

      pSample->Release();
      ASSERT(hr == S_OK);
      spBuffer->Release();
      ASSERT(hr == S_OK);
    }

    for (;;)
    {
      MFT_OUTPUT_DATA_BUFFER outputDataBuffer{};
      hr = MFCreateSample(&(outputDataBuffer.pSample));
      ASSERT(hr == S_OK);
      IMFMediaBuffer* pBuffer = nullptr;
      hr = MFCreateMemoryBuffer(cbOutputBytes, &pBuffer);
      ASSERT(hr == S_OK);
      hr = outputDataBuffer.pSample->AddBuffer(pBuffer);
      ASSERT(hr == S_OK);

      DWORD dwStatus;
      hr = transform->ProcessOutput(0, 1, &outputDataBuffer, &dwStatus);
      if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
      {
        pBuffer->Release();
        outputDataBuffer.pSample->Release();
        break;
      }
      ASSERT(hr == S_OK); // if this fails with E_NOTIMPL, then the CHUNK_SIZE is too big
      ASSERT(dwStatus == 0);

      IMFMediaBuffer* spBuffer = nullptr;
      hr = outputDataBuffer.pSample->ConvertToContiguousBuffer(&spBuffer);
      ASSERT(hr == S_OK);
      DWORD cbBytes = 0;
      hr = spBuffer->GetCurrentLength(&cbBytes);
      ASSERT(hr == S_OK);
      BYTE* pByteBuffer = nullptr;
      hr = spBuffer->Lock(&pByteBuffer, nullptr, nullptr);
      ASSERT(hr == S_OK);
      memcpy(&outPcmData[outputBytesProcessed], pByteBuffer, cbBytes);
      outputBytesProcessed += cbBytes;
      ASSERT(outputBytesProcessed <= roughEstimateSize);
      hr = spBuffer->Unlock();
      ASSERT(hr == S_OK);
      spBuffer->Release();
      pBuffer->Release();
      outputDataBuffer.pSample->Release();
    }

    if (pcmRemainingBytes == 0) // drain
    {
      DWORD cbOutputBytes2 = (DWORD)((int64_t)inputBufferSize * (outSampleRate * 2 * sizeof(f32)) / (inSampleRate * 2 * sizeof(f32)));
      // cbOutputBytes must be product of frambytes
      cbOutputBytes2 = (cbOutputBytes2 + ((2 * sizeof(f32)) - 1)) / (2 * sizeof(f32)) * (2 * sizeof(f32));

      hr = transform->ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM, NULL);
      ASSERT(hr == S_OK);
      hr = transform->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, NULL);
      ASSERT(hr == S_OK);

      for (;;)
      {
        MFT_OUTPUT_DATA_BUFFER outputDataBuffer{};
        hr = MFCreateSample(&(outputDataBuffer.pSample));
        ASSERT(hr == S_OK);
        IMFMediaBuffer* pBuffer = nullptr;
        hr = MFCreateMemoryBuffer(cbOutputBytes2, &pBuffer);
        ASSERT(hr == S_OK);
        hr = outputDataBuffer.pSample->AddBuffer(pBuffer);
        ASSERT(hr == S_OK);

        DWORD dwStatus;
        hr = transform->ProcessOutput(0, 1, &outputDataBuffer, &dwStatus);
        if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
        {
          pBuffer->Release();
          outputDataBuffer.pSample->Release();
          break;
        }
        ASSERT(hr == S_OK);
        ASSERT(dwStatus == 0);

        IMFMediaBuffer* spBuffer = nullptr;
        hr = outputDataBuffer.pSample->ConvertToContiguousBuffer(&spBuffer);
        ASSERT(hr == S_OK);
        DWORD cbBytes = 0;
        hr = spBuffer->GetCurrentLength(&cbBytes);
        ASSERT(hr == S_OK);
        BYTE* pByteBuffer = nullptr;
        hr = spBuffer->Lock(&pByteBuffer, nullptr, nullptr);
        ASSERT(hr == S_OK);
        memcpy(&outPcmData[outputBytesProcessed], pByteBuffer, cbBytes);
        outputBytesProcessed += cbBytes;
        ASSERT(outputBytesProcessed <= roughEstimateSize);
        hr = spBuffer->Unlock();
        ASSERT(hr == S_OK);
        spBuffer->Release();
        pBuffer->Release();
      }
      break;
    }
  }

  delete pcmData;
  pcmData = reinterpret_cast<f32*>(outPcmData);
  pcmMonoLength = outputBytesProcessed / sizeof(f32) / 2;

#else
  // This is the old resampling windows code. It will refuse to process data > 100MB.
  static IMFTransform* transform;
  static IMFMediaType* inputMediaType;
  static IMFMediaType* outputMediaType;
  HRESULT hr;
  if (transform == nullptr)
  {
    hr = CoInitialize(nullptr);
    ASSERT(hr == S_OK || S_FALSE /*was already initialized*/);

    IUnknown* transformUnk;
    const HRESULT result = CoCreateInstance(CLSID_CResamplerMediaObject, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, reinterpret_cast<void**>(&transformUnk));
    ASSERT(result == S_OK);
    transformUnk->QueryInterface(IID_PPV_ARGS(&transform));

    hr = MFCreateMediaType(&inputMediaType);
    ASSERT(hr == S_OK);
    inputMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
    inputMediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_Float);
    inputMediaType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, 2);
    inputMediaType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, sizeof(f32) * 2);
    inputMediaType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 32);
    inputMediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE);

    hr = MFCreateMediaType(&outputMediaType);
    ASSERT(hr == S_OK);
    inputMediaType->CopyAllItems(outputMediaType);
  }

  hr = inputMediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, inSampleRate);
  ASSERT(hr == S_OK);
  hr = inputMediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, sizeof(f32) * 2 * inSampleRate);
  ASSERT(hr == S_OK);

  hr = transform->SetInputType(0, inputMediaType, 0);
  ASSERT(hr == S_OK);

  hr = outputMediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, outSampleRate);
  ASSERT(hr == S_OK);
  hr = outputMediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, sizeof(f32) * 2 * outSampleRate);
  ASSERT(hr == S_OK);

  hr = transform->SetOutputType(0, outputMediaType, 0);
  ASSERT(hr == S_OK);

  hr = transform->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
  ASSERT(hr == S_OK);
  hr = transform->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
  ASSERT(hr == S_OK);
  hr = transform->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
  ASSERT(hr == S_OK);

  const u32 inputBufferSize = sizeof(f32) * pcmMonoLength * 2;
  const f32 sampleRatio = f32(outSampleRate) / f32(inSampleRate);

  IMFMediaBuffer* rInBuffer;
  hr = MFCreateMemoryBuffer(inputBufferSize, &rInBuffer);
  ASSERT(hr == S_OK);

  BYTE* inByteBuffer = nullptr;
  hr = rInBuffer->Lock(&inByteBuffer, nullptr, nullptr);
  ASSERT(hr == S_OK);
  memcpy(inByteBuffer, pcmData, inputBufferSize);
  hr = rInBuffer->Unlock();
  ASSERT(hr == S_OK);

  hr = rInBuffer->SetCurrentLength(inputBufferSize);
  ASSERT(hr == S_OK);

  IMFSample* rInSample;
  hr = MFCreateSample(&rInSample);
  ASSERT(hr == S_OK);
  hr = rInSample->AddBuffer(rInBuffer);
  ASSERT(hr == S_OK);

  hr = transform->ProcessInput(0, rInSample, 0);
  ASSERT(hr == S_OK);

  hr = rInBuffer->Release();
  //ASSERT(hr == S_OK);
  hr = rInSample->Release();
  //ASSERT(hr == S_OK);

  MFT_OUTPUT_DATA_BUFFER outDataBuffer{};
  hr = MFCreateSample(&outDataBuffer.pSample);
  ASSERT(hr == S_OK);
  IMFMediaBuffer* outBuffer;
  const u32 estimatedOutputBufferSize = u32(ceilf(inputBufferSize * sampleRatio) + (sizeof(f32)));
  hr = MFCreateMemoryBuffer(estimatedOutputBufferSize, &outBuffer);
  ASSERT(hr == S_OK);
  hr = outDataBuffer.pSample->AddBuffer(outBuffer);
  ASSERT(hr == S_OK);
  outDataBuffer.dwStreamID = 0;
  outDataBuffer.dwStatus = 0;
  outDataBuffer.pEvents = nullptr;

  DWORD rStatus;
  hr = transform->ProcessOutput(0, 1, &outDataBuffer, &rStatus);
  ASSERT(hr == S_OK); // if this fails with E_NOTIMPL, then the CHUNK_SIZE is too big
  ASSERT(rStatus == 0);

  hr = outBuffer->Release();
  //ASSERT(hr == S_OK);
  hr = outDataBuffer.pSample->ConvertToContiguousBuffer(&outBuffer);
  ASSERT(hr == S_OK);
  DWORD outputBufferSize;
  hr = outBuffer->GetCurrentLength(&outputBufferSize);
  ASSERT(hr == S_OK);

  pcmData = reinterpret_cast<f32*>(realloc(pcmData, outputBufferSize));

  BYTE* outByteBuffer = nullptr;
  hr = outBuffer->Lock(&outByteBuffer, nullptr, nullptr);
  ASSERT(hr == S_OK);
  memcpy(pcmData, outByteBuffer, outputBufferSize);
  hr = outBuffer->Unlock();
  ASSERT(hr == S_OK);

  pcmMonoLength = outputBufferSize / sizeof(f32) / 2;
  hr = outBuffer->Release();
  //ASSERT(hr == S_OK);
  hr = outDataBuffer.pSample->Release();
  //ASSERT(hr == S_OK);

#endif

#endif // PLATFORM_WINDOWS

#endif //SHR3D_AUDIO_SDL
}
