
/*
 * --- GSMP-COPYRIGHT-NOTE-BEGIN ---
 * 
 * This copyright note is auto-generated by ./scripts/Create-CopyPatch.
 * Please add additional copyright information _after_ the line containing
 * the GSMP-COPYRIGHT-NOTE-END tag. Otherwise it might get removed by
 * the ./scripts/Create-CopyPatch script. Do not edit this copyright text!
 * 
 * GSMP: pcm/src/OggVorbisStream.cc
 * General Sound Manipulation Program is Copyright (C) 2000 - 2004
 *   Valentin Ziegler and René Rebe
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2. A copy of the GNU General
 * Public License can be found in the file LICENSE.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANT-
 * ABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 * 
 * --- GSMP-COPYRIGHT-NOTE-END ---
 */

#include "Logging.hh"
#include "Limits.hh"

#include "OggVorbisStream.hh"

using namespace GSMP;

GSMP::Logger <DefaultLogging_OggVorbis> GSMP::OggVorbisLog;

/*******************
 * OggVorbisStream *
 *******************/

OggVorbisFileStream::OggVorbisFileStream ()
{
  m_file = 0;
  m_stream_pos = 0;
}

OggVorbisFileStream::~OggVorbisFileStream ()
{
  Q_LOG(OggVorbisLog) << std::endl;
  
  if (m_file != 0) {
    Q_LOG(OggVorbisLog)
	     << " closing OggVorbis file in dtor" << std::endl;
    
    ov_clear (&m_vorbis_file);
    // fclose (m_file); // TODO: verify if needed
    m_file = 0;
  }
}

bool OggVorbisFileStream::Open (const std::string& fname)
{
  if (m_file != 0) {
    Q_LOG(OggVorbisLog)
	     << " file already opened" << std::endl;
    return false;
  }
  
  m_fname = fname;
  
  Q_LOG(OggVorbisLog) << m_fname << std::endl;
  
  if ( (m_file = fopen (m_fname.c_str (), "r") ) < 0) {
    Q_LOG(OggVorbisLog)
	     << " can't open file: " << fname << std::endl;
    
    m_file = 0;
    return false;
  }
  
  Q_LOG(OggVorbisLog)
    	   << "opend file: " << m_file << std::endl;
  
  if (ov_open (m_file, &m_vorbis_file, NULL, -1) < 0) {
    Q_LOG(OggVorbisLog)
	     << " input does not appear to be an Ogg bitstream" << std::endl;
    
    fclose (m_file);
    return false;
  }
  
  if (! ov_seekable (&m_vorbis_file) ) {
    Q_LOG(OggVorbisLog)
	     << " input is not seekable" << std::endl;
    
    ov_clear (&m_vorbis_file);
    fclose (m_file);
    //m_file = 0; // TODO: do we need this?
    return false;
  }
  
  // TODO: have we to free this ??
  vorbis_info* vi = ov_info (&m_vorbis_file, -1);
  
  m_channels = vi -> channels;
  m_rate = vi -> rate;
  
  ogg_int64_t pcm_total = ov_pcm_total (&m_vorbis_file, -1);
  m_size = pcm_total;
  
  Q_LOG(OggVorbisLog)
	   << " recognized: "
	   << m_channels << " channels, "
	   << m_size << " samples." << std::endl;
  
  // if we are here, the file is open
  
  return true;
}

bool OggVorbisFileStream::IsOpen ()
{
  return m_file != 0;
}

bool OggVorbisFileStream::Close ()
{
  if (m_file == 0)
    return false;
  
  Q_LOG(OggVorbisLog)
	   << "m_file: " << m_file << std::endl;
  ov_clear (&m_vorbis_file);
  Q_LOG(OggVorbisLog)
	   << "m_file: " << m_file << std::endl;
  
  //fclose (m_file);
  m_file = 0;
  
  return true;
}

global_index OggVorbisFileStream::Size ()
{
  if (IsOpen () )
    return m_size;
  else {
    Q_LOG(OggVorbisLog)
	     << " unknown if not opened!" << std::endl;
    return 0;
  }
}

channel_index OggVorbisFileStream::Channels ()
{
  if (IsOpen () )
    return m_channels;
  else {
    Q_LOG(OggVorbisLog)
	     << " unknown if not opened!" << std::endl;
    return 0;
  }
}

sample_rate OggVorbisFileStream::Rate ()
{
  if (IsOpen () )
    return m_rate;
  else {
    Q_LOG(OggVorbisLog)
	     << " unknown if not opened!" << std::endl;
    return 0;
  }
}

local_index
OggVorbisFileStream::Read (local_index size,
			   BasicStream* dest)
{
  
  Q_LOG(OggVorbisLog)
	   << size << std::endl;
  
  local_index t_processed = 0;
  
  if (m_stream_pos < m_size)
    {
      local_index t_size =
	Utility::limit_max (size, m_size - m_stream_pos);
      
      int ret = 0;
      int read_bytes = 0;
      int needed_bytes = t_size * 2 * m_channels;
      
      int16_t t_data [t_size * m_channels];
      
      // Ogg/Vorbis seems to do the processing in a main-loop (whatever) and
      // returns only the bytes it has in this decodeing loop ...
      do
	{
	  Q_LOG(OggVorbisLog)
		   << " ov_read" << std::endl;
	  
	  ret = ov_read (&m_vorbis_file, (char*) (t_data) + read_bytes,
			 needed_bytes - read_bytes,
			 BasicStream::BufferType::endianess_type::IsBigendian, // endieness
			 2, // word
			 1, // signed
			 &m_vorbis_section);
	  
	  if (ret < 0)
	    Q_LOG(OggVorbisLog)
		     << " read error: " << ret << "(ignored ...)" << std::endl;
	  
	  if (ret > 0)
	    read_bytes += ret;
	}
      while (ret > 0 && read_bytes < needed_bytes);
      
      // copy and scale pcm data
      Q_LOG(OggVorbisLog)
	       << " scale, deinterleave and copy "
	       << read_bytes << " bytes" << std::endl;
      
      for (channel_index t = 0; t < m_channels; ++t) {
	BasicStream::BufferType* buffer = (*dest) [t];
	for (local_index i = 0; i < t_size; ++i) {
	  (*buffer)[i] = PcmConvert<int16_t,
	    BasicStream::BufferType::pcm_type>::Convert(t_data [i * m_channels + t]);
	  (*buffer)[i] = t_data [i * m_channels + t] << 8;
	}
      }
      
      t_processed += t_size;
      m_stream_pos += t_size;
    }
  
  if (t_processed < size) {
    Q_LOG(OggVorbisLog)
	     << " not all read -> zeroing ..." << std::endl;
    
    for (channel_index t = 0; t < m_channels; ++t) {
      BasicStream::BufferType* buffer = (*dest) [t];
      buffer->WriteZero (size - t_processed);
    }
    
    t_processed = size;
  }
  
  dest->SetUsedSize (size);
  
  return t_processed;
}

global_index OggVorbisFileStream::Seek (global_index pos)
{
  if (pos != m_stream_pos)
    {
      int ret = 0;
      
      ret = ov_pcm_seek (&m_vorbis_file, pos);
      if (ret < 0) {
	Q_LOG(OggVorbisLog)
		 << " error during search (pos: "
		 << pos << "): " << ret << "(ignored ...)" << std::endl;
      }
      
      m_stream_pos = pos;
    }
  
  return m_stream_pos;
}
