// Copyright (c) 2021 Giorgos Vougioukas
//
// The license can be found in the LICENSE file.

#include "RSI/render_system.h"
#include "RSI/concurrency.h"
#include "RSI/data_bank.h"
#include "RSI/track.h"
#include "third_party/libebur128/ebur128/ebur128.h"

#include "WDL/fpcmp.h"
#include "WDL/db2val.h"

RSI_RenderSystem::RSI_RenderSystem()
  : m_killrenderthread(false)
  , m_killloadthread(false)
  , m_activetrack(1)
  , m_shiftstep(g_preferences->GetShiftStep())
  , m_tempostep(g_preferences->GetTempoStep())
  , m_shiftaltstep(g_preferences->GetShiftAltStep())
  , m_tempoaltstep(g_preferences->GetTempoAltStep())
  , m_fi(NULL)
  , m_loadprogress(0)
  , m_abortloading(false)
  , m_running(false)
{}

RSI_RenderSystem::~RSI_RenderSystem()
{
  m_desc.Empty(true);
  m_info.Empty(true);
  g_tracks.DeleteAll();
}

void RSI_RenderSystem::StartThread()
{
  WDL_ASSERT(!m_renderthreads.GetSize());
  WDL_ASSERT(!m_loadthreads.GetSize());

  int priority;
  m_killrenderthread = false;
  m_killloadthread = false;

  m_renderthreads.Resize(1);
  //m_renderthreads.Resize(RSI_GetCPUCores());
  for (int i = 0; i < m_renderthreads.GetSize(); i++)
  {
    ThreadDetails *rt = m_renderthreads.Get();
    rt[i].thread = (HANDLE)_beginthreadex(NULL, 0,
      RenderingThreadFunction, (void *)this, 0, &rt[i].id);

    priority = g_preferences->GetRenderSystemPriority();
    SetThreadPriority(rt[i].thread, priority);
  }

  m_loadthreads.Resize(1);
  for (int i = 0; i < m_loadthreads.GetSize(); i++)
  {
    ThreadDetails *lt = m_loadthreads.Get();
    lt[i].thread = (HANDLE)_beginthreadex(NULL, 0,
      LoadingThreadFunction, (void *)this, 0, &lt[i].id);

    priority = g_preferences->GetDiskIOPriority();
    SetThreadPriority(lt[i].thread, priority);
  }

  m_running = true;
}

void RSI_RenderSystem::StopThread()
{
  m_killrenderthread = true;
  for (int i = 0; i < m_renderthreads.GetSize(); i++)
  {
    ThreadDetails *rt = m_renderthreads.Get();
    WaitForSingleObject(rt[i].thread, INFINITE);
    CloseHandle(rt[i].thread);
    m_renderthreads.Resize(0);
  }

  m_killloadthread = true;
  for (int i = 0; i < m_loadthreads.GetSize(); i++)
  {
    ThreadDetails *lt = m_loadthreads.Get();
    WaitForSingleObject(lt[i].thread, INFINITE);
    CloseHandle(lt[i].thread);
    m_loadthreads.Resize(0);
  }

  m_running = false;
}

bool RSI_RenderSystem::IsRunning() const
{
  return m_running;
}

void RSI_RenderSystem::LoadFileTrack(const char *filename)
{
  WDL_MutexLock lock(&m_rendermutex);
  RSI_Track *trk = g_tracks.Get(m_activetrack);
  if (trk && trk->IsActive()) return;

  EjectTrack(m_activetrack);
  m_fi = CreateFileInput(filename);
}

void RSI_RenderSystem::SetActiveTrack(int track)
{
  m_activetrack = track;
}

bool RSI_RenderSystem::IsTrackAvailable() const
{
  RSI_Track *trk = g_tracks.Get(m_activetrack);
  if (trk && trk->IsActive()) return false;
  if (m_activetrack) return true;
  return false;
}

bool RSI_RenderSystem::IsTrackActive() const
{
  RSI_Track *trk = g_tracks.Get(m_activetrack);
  if (trk && trk->IsActive()) return true;
  return false;
}

int RSI_RenderSystem::GetActiveTrack() const
{
  return m_activetrack;
}

void RSI_RenderSystem::DecreaseShift(bool altstep)
{
  RSI_Track *trk = NULL;
  if (m_activetrack) trk = g_tracks.Get(m_activetrack);
  if (trk)
  {
    double step = m_shiftstep;
    if (altstep) step = m_shiftaltstep;
    double shift = trk->GetShift();
    shift = wdl_clamp(shift - step, 0.25, 4.0);
    trk->SetShift(shift);
  }
}

void RSI_RenderSystem::IncreaseShift(bool altstep)
{
  RSI_Track *trk = NULL;
  if (m_activetrack) trk = g_tracks.Get(m_activetrack);
  if (trk)
  {
    double step = m_shiftstep;
    if (altstep) step = m_shiftaltstep;
    double shift = trk->GetShift();
    shift = wdl_clamp(shift + step, 0.25, 4.0);
    trk->SetShift(shift);
  }
}

void RSI_RenderSystem::DecreaseTempo(bool altstep)
{
  RSI_Track *trk = NULL;
  if (m_activetrack) trk = g_tracks.Get(m_activetrack);
  if (trk)
  {
    double step = m_tempostep;
    if (altstep) step = m_tempoaltstep;
    double tempo = trk->GetTempo();
    tempo = wdl_clamp(tempo - step, 0.25, 4.0);
    trk->SetTempo(tempo);
  }
}

void RSI_RenderSystem::IncreaseTempo(bool altstep)
{
  RSI_Track *trk = NULL;
  if (m_activetrack) trk = g_tracks.Get(m_activetrack);
  if (trk)
  {
    double step = m_tempostep;
    if (altstep) step = m_tempoaltstep;
    double tempo = trk->GetTempo();
    tempo = wdl_clamp(tempo + step, 0.25, 4.0);
    trk->SetTempo(tempo);
  }
}

void RSI_RenderSystem::ResetShiftTempo()
{
  RSI_Track *trk = NULL;
  if (m_activetrack) trk = g_tracks.Get(m_activetrack);
  if (trk)
  {
    trk->SetShift(1.0);
    trk->SetTempo(1.0);
  }
}

void RSI_RenderSystem::Pause()
{
  WDL_MutexLock lock(&m_rendermutex);
  if (m_activetrack)
  {
    RSI_Track *trk = g_tracks.Get(m_activetrack);
    if (trk) trk->Pause();
  }
}

void RSI_RenderSystem::Rewind(bool state)
{
  WDL_MutexLock lock(&m_rendermutex);
  if (m_activetrack)
  {
    RSI_Track *trk = g_tracks.Get(m_activetrack);
    if (trk) trk->Rewind(state);
  }
}

void RSI_RenderSystem::FastForward(bool state)
{
  WDL_MutexLock lock(&m_rendermutex);
  if (m_activetrack)
  {
    RSI_Track *trk = g_tracks.Get(m_activetrack);
    if (trk) trk->FastForward(state);
  }
}

void RSI_RenderSystem::Play()
{
  WDL_MutexLock lock(&m_rendermutex);
  if (m_activetrack)
  {
    RSI_Track *trk = g_tracks.Get(m_activetrack);
    if (trk) trk->Play();
  }
}

void RSI_RenderSystem::PlayReverse()
{
  WDL_MutexLock lock(&m_rendermutex);
  if (m_activetrack)
  {
    RSI_Track *trk = g_tracks.Get(m_activetrack);
    if (trk) trk->PlayReverse();
  }
}

void RSI_RenderSystem::Stop()
{
  WDL_MutexLock lock(&m_rendermutex);
  if (m_activetrack)
  {
    RSI_Track *trk = g_tracks.Get(m_activetrack);
    if (trk) trk->Stop();
  }
}

void RSI_RenderSystem::StartStop(bool state)
{
  WDL_MutexLock lock(&m_rendermutex);
  if (m_activetrack)
  {
    RSI_Track *trk = g_tracks.Get(m_activetrack);
    if (trk) trk->StartStop(state);
  }
}

void RSI_RenderSystem::Seek(double time)
{
  WDL_MutexLock lock(&m_rendermutex);
  if (m_activetrack)
  {
    RSI_Track *trk = g_tracks.Get(m_activetrack);
    if (trk) trk->Seek(time);
  }
}

void RSI_RenderSystem::ToggleRepeat()
{
  if (m_activetrack)
  {
    RSI_Track *trk = g_tracks.Get(m_activetrack);
    if (trk) trk->ToggleRepeat();
  }
}

void RSI_RenderSystem::ToggleReversePlayback()
{
  WDL_MutexLock lock(&m_rendermutex);
  if (m_activetrack)
  {
    RSI_Track *trk = g_tracks.Get(m_activetrack);
    if (trk) trk->ToggleReversePlayback();
  }
}

void RSI_RenderSystem::ToggleHeadphone()
{
  WDL_MutexLock lock(&m_rendermutex);
  if (m_activetrack)
  {
    RSI_Track *trk = g_tracks.Get(m_activetrack);
    if (trk) trk->ToggleHeadphone();
  }
}

void RSI_RenderSystem::IncreaseVolume()
{
  if (m_activetrack)
  {
    RSI_Track *trk = g_tracks.Get(m_activetrack);
    if (trk) trk->IncreaseVolume();
  }
}

void RSI_RenderSystem::DecreaseVolume()
{
  if (m_activetrack)
  {
    RSI_Track *trk = g_tracks.Get(m_activetrack);
    if (trk) trk->DecreaseVolume();
  }
}

void RSI_RenderSystem::SetVolume(double db)
{
  if (m_activetrack)
  {
    RSI_Track *trk = g_tracks.Get(m_activetrack);
    if (trk) trk->SetVolume(db);
  }
}

void RSI_RenderSystem::EjectTrack(int track)
{
  WDL_MutexLock lock(&m_rendermutex);
  RSI_Track *trk = g_tracks.Get(track);
  if (trk && !trk->IsActive()) g_tracks.Delete(track);
}

void RSI_RenderSystem::EjectEverything()
{
  WDL_MutexLock lock(&m_rendermutex);
  for (int i = 0; i < g_tracks.GetSize(); i++)
  {
    RSI_Track *trk = g_tracks.Enumerate(i);
    if (trk) trk->Stop();
  }
  g_tracks.DeleteAll();
}

void RSI_RenderSystem::GetInfo(WDL_PtrList<WDL_FastString> **desc,
  WDL_PtrList<WDL_FastString> **info)
{
  if (!m_desc.GetSize() && !m_info.GetSize())
  {
    for (int i = 0; i < TRACKMAX; i++)
    {
      m_desc.Add(new WDL_FastString);
      m_info.Add(new WDL_FastString);
    }
  }

  WDL_FastString *dsc, *inf;
  dsc = m_desc.Get(TRACKNUM);
  inf = m_info.Get(TRACKNUM);

  if (m_activetrack) dsc->SetFormatted(512, "TRACK %d", m_activetrack);
  inf->Set("");

  RSI_Track *trk = NULL;
  if (m_activetrack) trk = g_tracks.Get(m_activetrack);

  if (!trk)
  {
    dsc = m_desc.Get(TRACKACTIVE);
    inf = m_info.Get(TRACKACTIVE);
    dsc->Set("");
    inf->Set("");

    for (int i = 0; i < g_tracks.GetSize(); i++)
    {
      int key;
      RSI_Track *t = g_tracks.Enumerate(i, &key);
      if (t && t->IsActive())
      {
        int per = 100 * (int)t->GetTime() / (int)t->GetTotalTime();
        dsc->Set("Active:");
        inf->AppendFormatted(512, "T%d/%d%% ", key, per);
      }
    }

    dsc = m_desc.Get(TRACKLOADED);
    inf = m_info.Get(TRACKLOADED);
    dsc->Set("");
    inf->Set("");

    for (int i = 0; i < g_tracks.GetSize(); i++)
    {
      int key;
      RSI_Track *t = g_tracks.Enumerate(i, &key);
      if (t && !t->IsActive())
      {
        dsc->Set("Loaded:");
        inf->AppendFormatted(512, "T%d ", key);
      }
    }

    for (int i = 1; i < TRACKMAX; i++)
    {
      if (i == TRACKACTIVE || i == TRACKLOADED) continue;
      else
      {
        dsc = m_desc.Get(i);
        inf = m_info.Get(i);
        dsc->Set("");
        inf->Set("");
      }
    }

    *desc = &m_desc;
    *info = &m_info;

    return;
  }

  dsc = m_desc.Get(TRACKTIME);
  inf = m_info.Get(TRACKTIME);

  dsc->Set("Time:");
  int curmin, cursec, curms, totmin, totsec;
  double curtime = trk->GetTime();
  double tottime = trk->GetTotalTime();
  curmin = (int)curtime / 60;
  cursec = (int)curtime % 60;
  curms = (int)((curtime - (int)curtime) * 1000);
  totmin = (int)tottime / 60;
  totsec = (int)tottime % 60;
  inf->SetFormatted(64, "%01d:%02d.%03d / %01d:%02d",
    curmin, cursec, curms, totmin, totsec);

  dsc = m_desc.Get(TRACKTITLE);
  inf = m_info.Get(TRACKTITLE);
  dsc->Set("Title:");
  inf->Set(trk->GetTitle());

  dsc = m_desc.Get(TRACKARTIST);
  inf = m_info.Get(TRACKARTIST);
  dsc->Set("Artist:");
  inf->Set(trk->GetArtist());

  dsc = m_desc.Get(TRACKACTIVE);
  inf = m_info.Get(TRACKACTIVE);
  dsc->Set("Active:");
  inf->Set("");

  for (int i = 0; i < g_tracks.GetSize(); i++)
  {
    int key;
    RSI_Track *t = g_tracks.Enumerate(i, &key);
    if (t && t->IsActive())
    {
      int per = 100 * (int)t->GetTime() / (int)t->GetTotalTime();
      inf->AppendFormatted(512, "T%d/%d%% ", key, per);
    }
  }

  dsc = m_desc.Get(TRACKLOADED);
  inf = m_info.Get(TRACKLOADED);
  dsc->Set("Loaded:");
  inf->Set("");

  for (int i = 0; i < g_tracks.GetSize(); i++)
  {
    int key;
    RSI_Track *t = g_tracks.Enumerate(i, &key);
    if (t && !t->IsActive()) inf->AppendFormatted(512, "T%d ", key);
  }

  for (int i = 1; i < m_info.GetSize(); i++)
  {
    if (!m_info.Get(i)->GetLength()) m_desc.Get(i)->Set("");
  }

  dsc = m_desc.Get(TRACKPITCHSHIFT);
  inf = m_info.Get(TRACKPITCHSHIFT);
  dsc->Set("Pitch shift:");
  inf->SetFormatted(512, "%.2f", trk->GetShift());

  dsc = m_desc.Get(TRACKTIMESTRETCH);
  inf = m_info.Get(TRACKTIMESTRETCH);
  dsc->Set("Time stretch:");
  inf->SetFormatted(512, "%.2f", trk->GetTempo());

  dsc = m_desc.Get(TRACKREPEAT);
  inf = m_info.Get(TRACKREPEAT);
  dsc->Set("Repeat:");
  inf->SetFormatted(512, "%s", trk->IsRepeat() ? "On" : "Off");

  dsc = m_desc.Get(TRACKVOLUME);
  inf = m_info.Get(TRACKVOLUME);
  dsc->Set("Volume:");
  if (WDL_DefinitelyGreaterThan(trk->GetVolume(), -150.0))
  {
    inf->SetFormatted(512, "%.2f dB", trk->GetVolume());
  }
  else { inf->Set("-inf dB"); }

  RSI_DataBankSlot *dbs = g_databank->GetSlot(m_activetrack - 1);
  if (dbs)
  {
    if (dbs->IsEBUR128Enabled())
    {
      int ebur128mode = dbs->GetEBUR128Mode();

      dsc = m_desc.Get(TRACKEMPTYLINE);
      inf = m_info.Get(TRACKEMPTYLINE);
      dsc->Set("---");
      inf->Set("");

      dsc = m_desc.Get(TRACKR128);
      inf = m_info.Get(TRACKR128);
      dsc->Set("EBU R128");
      inf->Set("");

      dsc = m_desc.Get(TRACKREFERENCE);
      inf = m_info.Get(TRACKREFERENCE);
      dsc->Set("Reference:");
      inf->SetFormatted(512, "%.0f LUFS", dbs->GetEBUR128Reference());

      if ((ebur128mode & EBUR128_MODE_M) == EBUR128_MODE_M)
      {
        dsc = m_desc.Get(TRACKMOMENTARY);
        inf = m_info.Get(TRACKMOMENTARY);
        dsc->Set("Momentary:");
        if (WDL_DefinitelyGreaterThan(dbs->GetEBUR128Momentary(), -150.0))
        {
          inf->SetFormatted(512, "%.2f LUFS", dbs->GetEBUR128Momentary());
        }
        else { inf->Set("-inf LUFS"); }
      }

      if ((ebur128mode & EBUR128_MODE_S) == EBUR128_MODE_S)
      {
        dsc = m_desc.Get(TRACKSHORTTERM);
        inf = m_info.Get(TRACKSHORTTERM);
        dsc->Set("Shortterm:");
        if (WDL_DefinitelyGreaterThan(dbs->GetEBUR128Shortterm(), -150.0))
        {
          inf->SetFormatted(512, "%.2f LUFS", dbs->GetEBUR128Shortterm());
        }
        else { inf->Set("-inf LUFS"); }
      }

      if ((ebur128mode & EBUR128_MODE_I) == EBUR128_MODE_I)
      {
        dsc = m_desc.Get(TRACKINTEGRATED);
        inf = m_info.Get(TRACKINTEGRATED);
        dsc->Set("Integrated:");
        if (WDL_DefinitelyGreaterThan(dbs->GetEBUR128Integrated(), -150.0))
        {
          inf->SetFormatted(512, "%.2f LUFS", dbs->GetEBUR128Integrated());
        }
        else { inf->Set("-inf LUFS"); }
      }

      if ((ebur128mode & EBUR128_MODE_LRA) == EBUR128_MODE_LRA)
      {
        dsc = m_desc.Get(TRACKRANGE);
        inf = m_info.Get(TRACKRANGE);
        dsc->Set("Range:");
        if (WDL_DefinitelyGreaterThan(dbs->GetEBUR128Range(), -150.0))
        {
          inf->SetFormatted(512, "%.2f LU", dbs->GetEBUR128Range());
        }
        else { inf->Set("-inf LU"); }
      }

      if ((ebur128mode & EBUR128_MODE_SAMPLE_PEAK) == EBUR128_MODE_SAMPLE_PEAK)
      {
        dsc = m_desc.Get(TRACKSAMPLEPEAK);
        inf = m_info.Get(TRACKSAMPLEPEAK);
        dsc->Set("Sample peak:");
        inf->SetFormatted(512, "%.6f (%.2f dBFS)", dbs->GetEBUR128SamplePeak(),
          VAL2DB(dbs->GetEBUR128SamplePeak()));
      }

      if ((ebur128mode & EBUR128_MODE_TRUE_PEAK) == EBUR128_MODE_TRUE_PEAK)
      {
        dsc = m_desc.Get(TRACKTRUEPEAK);
        inf = m_info.Get(TRACKTRUEPEAK);
        dsc->Set("True peak:");
        inf->SetFormatted(512, "%.6f (%.2f dBTP)", dbs->GetEBUR128TruePeak(),
          VAL2DB(dbs->GetEBUR128TruePeak()));
      }

      dsc = m_desc.Get(TRACKGAIN);
      inf = m_info.Get(TRACKGAIN);
      dsc->Set("Gain:");
      if (WDL_DefinitelyGreaterThan(dbs->GetEBUR128Gain(), -150.0))
      {
        inf->SetFormatted(512, "%.2f dB", dbs->GetEBUR128Gain());
      }
      else { inf->Set("-inf dB"); }

      dsc = m_desc.Get(TRACKNORMALIZATION);
      inf = m_info.Get(TRACKNORMALIZATION);
      dsc->Set("Normalization:");
      inf->Set(dbs->IsEBUR128DowndwardOnly() ? "Downward-only" : "Downward/upward");
    }
  }

  *desc = &m_desc;
  *info = &m_info;
}

bool RSI_RenderSystem::IsLoadingInProgress() const
{
  return m_fi != NULL;
}

int RSI_RenderSystem::LoadingProgress() const
{
  return m_loadprogress;
}

void RSI_RenderSystem::AbortLoading()
{
  m_abortloading = true;
}

void RSI_RenderSystem::AudioStreamerData(WDL_TypedBuf<SAM> *output, int frame_count, int nch)
{
  WDL_ASSERT(nch == 2 || nch == 4);
  memset(output->Get(), 0, frame_count * sizeof(SAM));
  int act = g_tracks.GetSize();
  if (!act) return;

  for (int i = 0; i < g_tracks.GetSize(); i++)
  {
    int written = 0;
    BQ_Block *blk = NULL;
    RSI_Track *trk = g_tracks.Enumerate(i);

    switch (nch)
    {
    case 2:
      {
        while (written < frame_count)
        {
          if (trk && trk->IsActive() && !trk->IsPaused() && trk->m_bq.GetBlock(&blk))
          {
            if (blk->samq.Available() > frame_count - written)
            {
              int sz = frame_count - written;

              for (int j = 0; j < sz; j++)
              {
                blk->samq.Get()[j] *= DB2VAL(trk->GetVolume());
              }

              for (int j = 0; j < sz; j++)
              {
                output->Get()[written + j] += blk->samq.Get()[j];
              }

              written += sz;

              blk->samq.Advance(sz);
              blk->samq.Compact();

              if (blk->samq.Available()) trk->m_bq.ReturnBlock(blk);
              else trk->m_bq.DisposeBlock(blk);
            }
            else
            {
              int sz = blk->samq.GetSize();

              for (int j = 0; j < sz; j++)
              {
                blk->samq.Get()[j] *= DB2VAL(trk->GetVolume());
              }

              for (int j = 0, k = 0; j < sz; j++)
              {
                output->Get()[written + j] += blk->samq.Get()[j];
              }

              written += sz;

              blk->samq.Clear();
              trk->m_bq.DisposeBlock(blk);
            }
          }
          else
          {
            break;
          }
        }
      } break;
    case 4:
      {
        while (written < frame_count)
        {
          if (trk && trk->IsActive() && !trk->IsPaused() && trk->m_bq.GetBlock(&blk))
          {
            if (blk->samq.Available() * 2 > frame_count - written)
            {
              int sz = frame_count - written;

              for (int j = 0; j < sz / 2; j++)
              {
                blk->samq.Get()[j] *= DB2VAL(trk->GetVolume());
              }

              for (int j = 0, k = 0; j < sz;)
              {
                if (!trk->IsHeadphone())
                {
                  output->Get()[written + j] += blk->samq.Get()[k];
                  output->Get()[written + j + 1] += blk->samq.Get()[k + 1];
                  j += 4;
                  k += 2;
                }
                else
                {
                  output->Get()[written + j + 2] += blk->samq.Get()[k];
                  output->Get()[written + j + 3] += blk->samq.Get()[k + 1];
                  j += 4;
                  k += 2;
                }
              }

              written += sz;

              blk->samq.Advance(2 * sz / nch);
              blk->samq.Compact();

              if (blk->samq.Available()) trk->m_bq.ReturnBlock(blk);
              else trk->m_bq.DisposeBlock(blk);
            }
            else
            {
              int sz = blk->samq.GetSize() * 2;

              for (int j = 0; j < sz / 2; j++)
              {
                blk->samq.Get()[j] *= DB2VAL(trk->GetVolume());
              }

              for (int j = 0, k = 0; j < sz;)
              {
                if (!trk->IsHeadphone())
                {
                  output->Get()[written + j] += blk->samq.Get()[k];
                  output->Get()[written + j + 1] += blk->samq.Get()[k + 1];
                  j += 4;
                  k += 2;
                }
                else
                {
                  output->Get()[written + j + 2] += blk->samq.Get()[k];
                  output->Get()[written + j + 3] += blk->samq.Get()[k + 1];
                  j += 4;
                  k += 2;
                }
              }

              written += sz;

              blk->samq.Clear();
              trk->m_bq.DisposeBlock(blk);
            }
          }
          else
          {
            break;
          }
        }
      } break;
    }
  }
}

int RSI_RenderSystem::Rendering()
{
  for (int i = 0; i < g_tracks.GetSize(); i++)
  {
    int key;
    RSI_Track *trk = g_tracks.Enumerate(i, &key);

    if (trk->IsDrained())
    {
      trk->Stop();
      if (g_preferences->WantAutoEjectTrack()) EjectTrack(key);
    }

    while (trk->WantMore())
    {
      if (trk->IsPlaybackReverse())
      {
        if (!trk->DoBufferingReverse()) break;
      }
      else
      {
        if (!trk->DoBuffering()) break;
      }
    }
  }

  return 1;
}

int RSI_RenderSystem::Loading()
{
  if (m_fi)
  {
    int nsam = 0;
    int databankslot = m_activetrack - 1;
    WDL_TypedBuf<SAM> buffer;
    buffer.Resize(4096);

    RSI_DataBankSlot *dbs = g_databank->GetSlot(databankslot);

    if (dbs)
    {
      dbs->Reset();
      dbs->Allocate(m_fi->GetLength());
      do
      {
        nsam = m_fi->GetSamples(buffer.Get(), buffer.GetSize());
        m_loadprogress = 100 * (int)m_fi->GetPosition() / (int)m_fi->GetLength();
        dbs->AddSamples(buffer.Get(), nsam);
        if (m_abortloading) break;
      } while (nsam);

      if (m_abortloading)
      {
        dbs->Reset();
        m_abortloading = false;
      }
      else
      {
        dbs->PrepareIndexes();
        g_tracks.Insert(m_activetrack, new RSI_Track(databankslot,
          m_fi->GetFileName(), g_preferences->WantTrackRepeat()));
      }
    }

    delete m_fi; m_fi = NULL;
  }

  return 1;
}

unsigned int WINAPI RSI_RenderSystem::RenderingThreadFunction(void *arg)
{
#if defined(_WIN32)
  CoInitialize(NULL);
#endif

  RSI_RenderSystem *self = (RSI_RenderSystem *)arg;

  if (WDL_NORMALLY(self))
  {
    int sleepstep = g_preferences->GetRenderSystemSleepStep();

    self->m_killrenderthread = false;

    while (!self->m_killrenderthread)
    {
      self->m_rendermutex.Enter();
      while (!self->Rendering());
      self->m_rendermutex.Leave();
      Sleep(sleepstep);
    }
  }

#if defined(_WIN32)
  CoUninitialize();
#endif

  return 0;
}

unsigned int WINAPI RSI_RenderSystem::LoadingThreadFunction(void *arg)
{
#if defined(_WIN32)
  CoInitialize(NULL);
#endif

  RSI_RenderSystem *self = (RSI_RenderSystem *)arg;

  if (WDL_NORMALLY(self))
  {
    int sleepstep = g_preferences->GetDiskIOSleepStep();

    self->m_killloadthread = false;

    while (!self->m_killloadthread)
    {
      self->m_loadmutex.Enter();
      while (!self->Loading());
      self->m_loadmutex.Leave();
      Sleep(sleepstep);
    }
  }

#if defined(_WIN32)
  CoUninitialize();
#endif

  return 0;
}
