/***************************************************************************
**
**  This file is part of GeopsyCore.
**
**  GeopsyCore 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, either version 3 of the License, or
**  (at your option) any later version.
**
**  GeopsyCore is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**  GNU General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2003-11-10
**  Copyright: 2003-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <math.h>
#include <QGpCoreTools.h>
#include <mseed.h>

#include "SubSignalPool.h"
#include "SignalDatabase.h"
#include "GeopsyCoreEngine.h"
#include "Gse.h"
#include "SEGYTraceHeader.h"
#include "SACHeader.h"
#include "MiniSeedReader.h"
#include "MetaDataFactory.h"
#include "AbstractFileFormat.h"
#include "Gse.h"
#include "GuralpCompressedBlock.h"
#include "StackCount.h"
#include "OriginalFileName.h"
#include "GuralpRecords.h"
#include "TimePick.h"
#include "AsciiSignalFormat.h"
#include "GeopsySignalHeader.h"
#include "CompatibilityTimeReference.h"

namespace GeopsyCore {

/*!
  \class Signal GeoSignal.h
  \brief A signal with samples in double precision with metadata (name, coordinates,...)

*/

const QString Signal::xmlSignalTag="Signal";

/*!
  Construct a signal and add it to database \a db
*/
Signal::Signal(SignalDatabase * db) :
    DoubleSignal (), SharedObject(), XMLClass()
{
  TRACE;
  _id=0;
  _component=Vertical;
  _countPerVolt=1.0;
  _voltPerUnit=1.0;
  _amplitudeUnit=Velocity;
  _numberInFile=-1;
  _file=nullptr;
  _offsetInFile=0;
  _byteIncrement=0;
  _isReadOnlySamples=false;
  _isHeaderModified=false;
  resetAmplitudes();
  _db=db;
  _db->addSignal(this); // _id is automatically set here
}

/*!
  Create a temporary copy of original
  This signal will be deleted once the last subpool
  containing it will be deleted. Samples are not copied.
*/
Signal::Signal (const Signal& p, SignalDatabase * db) :
  DoubleSignal(p), SharedObject(), XMLClass()
{
  TRACE;
  copyBasicProperties(p);
  resetAmplitudes();

  if(db)
    _db=db;
  else
    _db=p._db;
  _db->addSignal(this); // _id is automatically set here
  setTemporary();
}

Signal::~Signal()
{
  TRACE;
  ASSERT(!_db || referenceCount()<=0); // When the database is deleted, all signals are forced to be deleted.
  _optionalData.clear();
  if(_db) _db->signalDeleted(this);
}

/*!
  Sets signal ID. Called only by SignalDatabase::addSignal() (with id=-1) and when loading from an XML stream (id>-1).
*/
void Signal::setId(int id)
{
  TRACE;
  _id=-1; // remove this signal from the list of ids
  _id=_db->uniqueId(id);
}

/*!
  Called by copy contructor, but can also be called for a new signal created
  with other constructor (before first allocation of data samples)

  Usually initial reference count is set to 0.
  We decrease it to -1 without checking and deleting
  Adding signal to db set its reference count to 0
  Next the temporary signal will be added to one or more visual subpool
  When the last visual subpool is closed, the count drops to 0 and the
  signal is deleted, the destructor removes it from database automatically.
  If the signal is not displayed or added to a subpool then you must delete
  it manually.
*/
void Signal::setTemporary()
{
  TRACE;
  _file=nullptr;
  _numberInFile=0;
  _offsetInFile=0;
  _byteIncrement=0;
  _isReadOnlySamples=false;
  _isHeaderModified=true;
  removeReference(); /* Adding signal to db set its reference count to 1
                      We decrease it to 0 without checking and deleting
                      Next the temporary signal will be added to one or more visual subpool
                      When the last visual subpool is closed, the count drops to 0 and the
                      signal is deleted, the destructor remove it from database automatically. */

}

void Signal::copyBasicProperties(const Signal& o)
{
  TRACE;
  _startTime=o._startTime;
  _timeRange=o._timeRange;
  _receiver=o._receiver;
  _utmZone=o._utmZone;
  _name=o._name;
  _component=o._component;
  _countPerVolt=o._countPerVolt;
  _voltPerUnit=o._voltPerUnit;
  _amplitudeUnit=o._amplitudeUnit;
  _optionalData=o._optionalData;
}

/*!
  General function to be called before using the samples.
*/
double * Signal::lockSamples()
{
  TRACE;
  lockAdmin();
  if(isAllocated()) {
    lockData();
    unlockAdmin();
    return samples();
  }
  if(isSaved() || !file() || file()->format()==SignalFileFormat::Temporary) {
    if(GeopsyCoreEngine::instance()->cache()->makeAvailable(this)) {
      lockData();
      unlockAdmin();
      return samples();
    } else {
      unlockAdmin();
      return nullptr;
    }
  }
  if(GeopsyCoreEngine::instance()->cache()->makeAvailable(this)) {
    if(load(samples())) {
      lockData();
      unlockAdmin();
      return samples();
    } else {
      freeSamples();
    }
  }
  unlockAdmin();
  return nullptr;
}

const double * Signal::constLockSamples() const
{
  TRACE;
  lockAdmin();
  if(isAllocated()) {
    constLockData();
    unlockAdmin();
    return samples();
  }
  if(isSaved() || !file()) {
    if(GeopsyCoreEngine::instance()->cache()->makeAvailable(this)) {
      constLockData();
      unlockAdmin();
      return samples();
    } else {
      unlockAdmin();
      return nullptr;
    }
  }
  if(GeopsyCoreEngine::instance()->cache()->makeAvailable(this)) {
    if(load(samples())) {
      constLockData();
      unlockAdmin();
      return samples();
    } else {
      // Signal has just been allocated by makeAvailable, no modification to be lost
      // nor any usage of the sample pointer possible.
      const_cast<Signal *>(this)->freeSamples();
    }
  }
  unlockAdmin();
  return nullptr;
}

bool Signal::loadGeopsySignal(double * samples) const
{
  TRACE;
  QFile f(_file->name());
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading Geopsy Signal values: unable to open file %1\n").arg(_file->name()) );
    return false;
  }
  f.seek(_offsetInFile);
  GeopsySignalTraceHeader h;
  f.read(h.raw, GEOPSYSIGNAL_TRACEHEADERSIZE);
  if(!h.isValid()) {
    App::log(tr("Loading Geopsy Signal values: wrong format for file %1\n").arg(_name) );
    return false;
  }
  if(h.field.version<GEOPSYSIGNAL_VERSION) {
    // Version 2 had no trace header, we go back to original pos
    f.seek(_offsetInFile);
    App::log(tr("Loading Geopsy Signal values: file generated with an old release with possible issues about conversion factors. "
                        "Carefully check the amplitudes and conversion factors.\n"));
    h.field.unitPerCount=unitPerCount(); // compatibility, do not touch original amplitudes
  }
  qint64 n=sizeof(double)*_nSamples;
  if(f.read((char *)samples, n)!=n) {
    return false;
  } else {
    // Correction of conversion factor if it has been changed in the database
    if(h.field.unitPerCount!=unitPerCount()) {
      double factor=unitPerCount()/h.field.unitPerCount;
      App::log(1, tr("Loading Geopsy Signal values: fixing conversion factor, from %1 to %2 (* %3)\n")
                        .arg(h.field.unitPerCount).arg(unitPerCount()).arg(unitPerCount()/h.field.unitPerCount));
      for(int i=0; i<_nSamples; i++) {
        samples[i]*=factor;
      }
    }
    return true;
  }
}

bool Signal::loadSeg2(double * samples) const
{
  TRACE;
  QFile f(_file->name());
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading SEG-2 signal values: unable to open file %1\n").arg(_file->name()) );
    return false;
  }
  f.seek(_offsetInFile);
  QByteArray buf=f.read(32);
  qint16 id=*(reinterpret_cast<qint16 *>(buf.data()));
  if(id!=0x4422) {
    App::log(tr("Loading SEG-2 signal values: invalid signal\n") );
    return false;
  }

  int blocksize=*(reinterpret_cast<qint16 *>(buf.data()+2));
  int form=*(reinterpret_cast<qint32 *>(buf.data()+12));
  //printf("blocksize=%i, format=%i\n",blocksize,form);
  f.seek(_offsetInFile+blocksize);
  double conversionFactor=unitPerCount();
  switch (form) {
  case 1: {
      qint16 * val=new qint16[ _nSamples ];
      f.read((char *)val, sizeof(qint16)*_nSamples);
      for(int i=0; i<_nSamples; i++) {
        samples[i]=(double)val[i]*conversionFactor;
      }
      delete [] val;
    }
    break;
  case 2: {
      int * val=new qint32[_nSamples];
      f.read((char *)val, sizeof(qint32)*_nSamples);
      for(int i=0; i<_nSamples; i++) {
        samples[i]=(double)val[i]*conversionFactor;
      }
      delete [] val;
    }
    break;
  case 3: { // 20 bits floating-point value grouped by 4
      if(_nSamples % 4!=0) {
        App::log(tr(" ### ERROR ### : the number of samples must be a multiple of 4\n") );
        return false;
      }
      struct FourValues {
        quint16 exponents;
        qint16 s1, s2, s3, s4;
      };

      int n4=_nSamples >> 2;
      FourValues * val=new FourValues[n4];
      f.read((char *)val, sizeof(FourValues)*n4);
      for(int i=0; i < n4; i++) {
        *samples=(double)val[i].s1 * conversionFactor *
             (double) ((int)1 << (0x000F & val[i].exponents));
        samples++;
        *samples=(double)val[i].s2 * conversionFactor *
             (double) ((int)1 << ((0x00F0 & val[i].exponents) >> 4));
        samples++;
        *samples=(double)val[i].s3 * conversionFactor *
             (double) ((int)1 << ((0x0F00 & val[i].exponents) >> 8));
        samples++;
        *samples=(double)val[i].s4 * conversionFactor *
             (double) ((int)1 << ((0xF000 & val[i].exponents) >> 12));
        samples++;
      }
      delete [] val;
    }
    break;
  case 4: {
      ASSERT(sizeof(float)==4);
      float * val=new float[_nSamples];
      f.read((char *)val, sizeof(float)*_nSamples);
      for(int i=0; i<_nSamples; i++) {
        samples[i]=Number::toDouble(val[i])*conversionFactor;
      }
      delete [] val;
    }
    break;
  default:
    App::log(tr("Unrecognized format for SEG-2 numbers in signal %1: format=%2\n").arg(name()).arg(form) );
    return false;
  }
  return true;
}

bool Signal::loadSegD(double * samples) const
{
  TRACE;
  QFile f(_file->name());
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading SEG-D signal values: unable to open file %1\n").arg(_file->name()) );
    return false;
  }
  f.seek(_offsetInFile);
  QByteArray buf=f.read(32);
  qint16 id=*(reinterpret_cast<qint16 *>(buf.data()));
  if(id!=0x4422) {
    App::log(tr("Loading SEG-D signal values: Invalid signal\n") );
    return false;
  }

  int blocksize=*(reinterpret_cast<qint16 *>(buf.data()+2));
  int form=*(reinterpret_cast<qint32 *>(buf.data()+12));
  //printf("blocksize=%i, format=%i\n",blocksize,form);
  f.seek(_offsetInFile+blocksize);
  double conversionFactor=unitPerCount();
  switch (form) {
  case 1: {
      qint16 * val=new qint16[ _nSamples ];
      f.read((char *)val, sizeof(qint16)*_nSamples);
      for(int i=0; i<_nSamples; i++) {
        samples[i]=(double)val[i]*conversionFactor;
      }
      delete [] val;
    }
    break;
  case 2: {
      int * val=new qint32[_nSamples];
      f.read((char *)val, sizeof(qint32)*_nSamples);
      for(int i=0; i<_nSamples; i++) {
        samples[i]=(double)val[i]*conversionFactor;
      }
      delete [] val;
    }
    break;
  case 3: { // 20 bits floating-point value grouped by 4
      if(_nSamples % 4!=0) {
        App::log(tr(" ### ERROR ### : the number of samples must be a multiple of 4\n") );
        return false;
      }
      struct FourValues {
        quint16 exponents;
        qint16 s1, s2, s3, s4;
      };

      int n4=_nSamples >> 2;
      FourValues * val=new FourValues[n4];
      f.read((char *)val, sizeof(FourValues)*n4);
      for(int i=0; i < n4; i++) {
        *samples=(double)val[i].s1 * conversionFactor *
             (double) ((int)1 << (0x000F & val[i].exponents));
        samples++;
        *samples=(double)val[i].s2 * conversionFactor *
             (double) ((int)1 << ((0x00F0 & val[i].exponents) >> 4));
        samples++;
        *samples=(double)val[i].s3 * conversionFactor *
             (double) ((int)1 << ((0x0F00 & val[i].exponents) >> 8));
        samples++;
        *samples=(double)val[i].s4 * conversionFactor *
             (double) ((int)1 << ((0xF000 & val[i].exponents) >> 12));
        samples++;
      }
      delete [] val;
    }
    break;
  case 4: {
      ASSERT(sizeof(float)==4);
      float * val=new float[_nSamples];
      f.read((char *)val, sizeof(float)*_nSamples);
      for(int i=0; i<_nSamples; i++) {
        samples[i]=Number::toDouble(val[i])*conversionFactor;
      }
      delete [] val;
    }
    break;
  default:
    App::log(tr("Unrecognized format for SEG-D numbers in signal %1: format=%2\n").arg(name()).arg(form) );
    return false;
  }
  return true;
}

bool Signal::loadRadan(double * samples) const
{
  TRACE;
  FILE * f=fopen(_file->name().toLatin1().data(), "rb");
  if(!f) {
    App::log(tr("Loading Radan signal values: unable to open file %1\n").arg(_file->name()) );
    return false;
  }
  fseek(f, _offsetInFile, SEEK_SET);
  double conversionFactor=unitPerCount();
  if(_byteIncrement==1) {
    char val=0;
    for(int i=0; i<_nSamples; i++) {
      if(fread(&val, sizeof(char), 1, f)!=1) {
        App::log(tr("Loading Radan file %1: error reading value\n").arg(_file->name()));
        fclose(f);
        return false;
      }
      *samples=((int)val-127)*conversionFactor;
      samples++;
    }
  } else if(_byteIncrement==2) {
    App::log(tr("Loading signal values : RADAN files with 16 bits are not yet implemented\n"));
    fclose(f);
    return false;
  }
  fclose(f);
  return true;
}

bool Signal::loadSegY(double * samples, QDataStream::ByteOrder bo) const
{
  TRACE;
  QFile f(_file->name());
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading SEGY signal values : Unable to open file %1\n").arg(_file->name()) );
    return false;
  }
  QDataStream s(&f);
#if(QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
  s.setFloatingPointPrecision(QDataStream::SinglePrecision);
#endif
  s.setByteOrder(bo);
  f.seek(_offsetInFile);
  double conversionFactor=unitPerCount();
  switch (byteIncrement()) {
  case 1: // 4-byte IBM floats
    {
      int val;
      for(int i=0; i<_nSamples; i++) {
        s >> val;
        samples[i]=SEGYTraceHeader::IBMFloat2ieee(val)*conversionFactor;
      }
    }
    break;
  case 2: // 4-byte, two's complement integer
    {
      qint32 val;
      for(int i=0; i<_nSamples; i++) {
        s >> val;
        samples[i]=static_cast<double>(val)*conversionFactor;
      }
    }
    break;
  case 3: // 2-byte, two's complement integer
    {
      qint16 val;
      for(int i=0; i<_nSamples; i++) {
        s >> val;
        samples[i]=static_cast<double>(val)*conversionFactor;
      }
    }
    break;
  case 5: // 4-byte IEEE floats
    {
      float val;
      for(int i=0; i < _nSamples; i++) {
        s >> val;
        samples[i]=Number::toDouble(val)*conversionFactor;
      }
    }
    break;
  case 8: // 1-byte, two's complement integer
    {
      qint8 val;
      for(int i=0; i<_nSamples; i++) {
        s >> val;
        samples[i]=static_cast<double>(val)*conversionFactor;
      }
    }
    break;
  default:
    App::log(tr("SEGY signal values: unsupported sample data coding\n") );
    break;
  }
  return true;
}

bool Signal::loadSu(double * samples, QDataStream::ByteOrder bo) const
{
  TRACE;
  float val;

  QFile f(_file->name());
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading SU signal values : Unable to open file %1\n").arg(_file->name()) );
    return false;
  }
  QDataStream s(&f);
#if(QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
  s.setFloatingPointPrecision(QDataStream::SinglePrecision);
#endif
  s.setByteOrder(bo);
  f.seek(_offsetInFile);
  double conversionFactor=unitPerCount();
  for(int i=0; i < _nSamples; i++) {
    s >> val;
    samples[i]=Number::toDouble(val)*conversionFactor;
  }
  return true;
}

bool Signal::loadPasscalSegY(double * samples, QDataStream::ByteOrder bo) const
{
  TRACE;
  QFile f(_file->name());
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading PASSCAL SEGY signal values : Unable to open file %1\n").arg(_file->name()) );
    return false;
  }
  QDataStream s(&f);
  s.setByteOrder(bo);
  f.seek(_offsetInFile);
  double conversionFactor=unitPerCount();
  if(_byteIncrement==2) {
    qint16 val;
    for(int i=0; i < _nSamples; i++) {
      s >> val;
      samples[ i ]=val*conversionFactor;
    }
  } else {
    qint32 val;
    for(int i=0; i < _nSamples; i++) {
      s >> val;
      samples[ i ]=val*conversionFactor;
    }
  }
  return true;
}

bool Signal::loadRD3(double * samples) const
{
  TRACE;
  qint16 val;

  FILE *f=fopen(_file->name().toLatin1().data(), "rb");
  if(!f) {
    App::log(tr("Loading RD3 signal values: unable to open file %1\n").arg(_file->name()) );
    return false;
  }
  fseek(f, _offsetInFile, SEEK_SET);
  double conversionFactor=unitPerCount();
  for(int i=0; i < _nSamples; i++) {
    if(fread(&val, 2, 1, f)!=1) {
      App::log(tr("Loading RD3 file %1: error reading value\n").arg(_file->name()));
      fclose(f);
      return false;
    }
    *samples=(double) val * conversionFactor;
    samples++;
  }
  samples[ _nSamples-2 ]=samples[ _nSamples-3 ];
  samples[ _nSamples-1 ]=samples[ _nSamples-2 ];

  fclose(f);
  return true;
}

bool Signal::loadSac(double * samples, QDataStream::ByteOrder bo) const
{
  TRACE;
  QFile f(_file->name());
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading SAC signal values : Unable to open file %1\n").arg(_file->name()) );
    return false;
  }
  QDataStream s(&f);
#if(QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
  s.setFloatingPointPrecision(QDataStream::SinglePrecision);
#endif
  s.setByteOrder(bo);
  f.seek(_offsetInFile);
  float val;
  double conversionFactor=unitPerCount();
  for(int i=0; i<_nSamples; i++) {
    s >> val;
    samples[i]=Number::toDouble(val)*conversionFactor;
  }
  return true;
}

bool Signal::loadWav(double * samples) const
{
  TRACE;
  QFile f(_file->name());
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading WAVE PCM soundfile signal values: unable to open file %1\n").arg(_file->name()) );
    return false;
  }
  int bufLen=_offsetInFile*_nSamples;
  char * buf=new char[bufLen];
  char * bufPtr=buf;
  f.seek(44);
  qint64 n=sizeof(char)*bufLen;
  if(f.read(buf, n)!=n) {
    delete [] buf;
    return false;
  }
  int i;
  double conversionFactor=unitPerCount();
  switch(_byteIncrement) {
  case 1:
    break;
  case 2:
    for(i=0; i<_nSamples;i++, bufPtr+=_offsetInFile) {
      samples[i]=*((qint16*)bufPtr)*conversionFactor;
    }
  case 4:
    break;
  default:
    break;
  }
  delete [] buf;
  return true;
}

/*!
  Load samples from a GSE 2 file using GSE (working for 32 and 64 bit platforms)
*/
bool Signal::loadGse2(double * samples) const
{
  TRACE;
  QFile f(_file->name());
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading GSE 2 values : Unable to open file %1\n").arg(_file->name()) );
    return false;
  }
  f.seek(_offsetInFile);
  int cs;
  qint32 * decomp=new qint32 [ _nSamples ];
  if(!decomp) {
    App::log(tr("Loading GSE 2 values : Temporary data cannot be allocated (insufficient memory)\n") );
    return false;
  }
  if(_byteIncrement==4) {
    for(int i=0;i < _nSamples; i++) f.read((char *)(decomp+i), 4);
    QByteArray buf;
    while(!buf.startsWith("CHK2") && !f.atEnd()) {
      f.readLine();
    }
    if(buf.startsWith("CHK2")) {
      cs=buf.mid(4).toInt();
    } else {
      App::log(tr("Loading GSE 2 values: no checksum found in file %1.\n").arg(_file->name()) );
      cs=0;
    }
  } else {
    QByteArray buf, bufLine;
    buf.reserve(_nSamples);
    int iLine=1;
    bufLine=f.readLine();
    int wrongLength=0;
    int wrongLengthLine=-1;
    while(!bufLine.startsWith("CHK2") && ! f.atEnd()) {
      // Remove end-of-line characters ("\n" or "\r\n")
      if(bufLine.endsWith('\n')) {
        bufLine.chop(1);
      }
      if(bufLine.endsWith('\r')) {
        bufLine.chop(1);
      }
      if(bufLine.count()!=80) {
        if(wrongLengthLine==-1) {
          wrongLength=bufLine.count();
          wrongLengthLine=iLine;
        } else {
          App::log(tr("Loading GSE 2 values: incorrect data format in file %2\n"
                      "  length of line %1 (counted from last DAT2) must be constant (80 chars) instead of %3.\n")
                           .arg(wrongLengthLine).arg(_file->name()).arg(wrongLength));
          delete [] decomp;
          return false;
        }
      }
      buf+=bufLine;
      bufLine=f.readLine();
      iLine++;
    }
    // Check that only the last line has a wrong number of characters
    if(wrongLengthLine>-1 && wrongLengthLine!=iLine-1) {
      App::log(tr("Loading GSE 2 values: incorrect data format in file %2\n"
                  "  length of line %1 (counted from last DAT2) must be constant (80 chars) instead of %3.\n")
                       .arg(wrongLengthLine).arg(_file->name()).arg(wrongLength));
      delete [] decomp;
      return false;
    }
    if(bufLine.startsWith("CHK2")) {
      cs=buf.mid(4).toInt();
    } else {
      App::log(tr("Loading GSE 2 values: no checksum found in file %1.\n").arg(_file->name()) );
      cs=0;
    }
    if(_byteIncrement==6) Gse::decompress6(buf.count(), buf.data(), _nSamples, decomp);
    else if(_byteIncrement==7 || _byteIncrement==8) {
      App::log(tr("Loading GSE 2 values : decompress7 and decompress8 function are currently not "
                          "implemented. Please send a request to the developers.\n"));
      delete [] decomp;
      return false;
    } else {
      App::log(tr("Loading GSE 2 values: data format not supported in file %1.\n").arg(_file->name()));
      delete [] decomp;
      return false;
    }
    // remove first and second differences: only for CM files
    Gse::remdif(decomp, _nSamples);
    Gse::remdif(decomp, _nSamples);
  }

  // If necessary calculate and check the checksum
  /* I noticed two ways of computing the checksum:
    - the simplest way: just add all samples and let machine dependent
                        overflows limit the value.
    - the more complex way: that avoid overflow and take an overflow base at
                            100,000,000
  */
  if(cs!=0) {
    // The check sum is not always calculated in the same way, try the more complicated and the simplest
    int ncs1=Gse::checksum(decomp, _nSamples);
    if(ncs1!=cs) {
      int ncs2=0;
      for(int i=0; i<_nSamples; i++) ncs2+=decomp[i];
      if(ncs2!=cs) {
        App::log(tr("Loading GSE 2 values: bad checksum in file %4, read: %1, calculated: %2 or %3.\n").
                              arg(cs).arg(ncs1).arg(ncs2).arg(_file->name()));
      }
    }
  }
  // Transfert integer values into double using conversionFactor (recorder and sensor)
  double conversionFactor=unitPerCount();
  for(int i=0; i<_nSamples; i++)
    samples[i]=static_cast<double>(decomp[i])*conversionFactor;
  delete [] decomp;
  return true;
}

bool Signal::loadSismalp(double * samples) const
{
  TRACE;
  FILE * f=fopen(_file->name().toLatin1().data(), "rb");
  if(!f) {
    App::log(tr("Loading sismalp signal values : unable to open file %1.\n").arg(_file->name()) );
    return false;
  }
  uint nMantisse=0x000000FF & _byteIncrement;
  uint nExponent=(0x00000F00 & _byteIncrement) >> 8;
  uint maskM=0, maskE=0;
  uint j;
  for(j=0;j < nMantisse;j++) maskM=1 + (maskM << 1);
  for(j=0;j < nExponent;j++) maskE=256 + (maskE << 1);
  //printf("nMant=%u nExp=%u maskM=%x maskE=%x\n",nMantisse,nExponent,maskM,maskE);
  double conversionFactor=unitPerCount();
  if(nMantisse + nExponent <= 16) {
    fseek(f, _offsetInFile * 2, SEEK_SET);
    ASSERT(sizeof(qint16)==2);
    qint16 * val=new qint16[ _nSamples ];
    if(static_cast<int>(fread(val, 2, _nSamples, f))!=_nSamples) {
      App::log(tr("Loading sismalp signal values : error reading file %1.\n").arg(_file->name()) );
      return false;
    }
    if(nExponent==0) { // samples are signed
      int maxMant=(1 << (nMantisse-1))-1;
      for(int i=0; i < _nSamples; i++) {
        int mant=(int) ((qint16) maskM & val[ i ]);
        if(mant > maxMant) mant -= (maxMant + 1) << 1;
        samples[ i ]=(double) mant * conversionFactor;
      }
    } else {
      int zero ;
      zero=1 << nMantisse;
      zero=(int) pow(2.0, (double) nMantisse)/2;
      for(int i=0; i < _nSamples; i++) {
        //uint exp=(maskE & val[i]) >> 8;
        int mant=(int) ((qint16) maskM & val[ i ]);

        //printf("%i mant=%i exp=%i",i,mant,exp);
        //mant=(mant << exp);
        //printf(" mant=%i\n",mant);
        samples[ i ]=((double) mant-(double) zero) * conversionFactor;
      }
    }
    delete [] val;
  } else {
    fseek(f, _offsetInFile * 4, SEEK_SET);
    qint32 * val=new qint32[_nSamples];
    if(static_cast<int>(fread(val, sizeof(qint32), _nSamples, f))!=_nSamples) {
      App::log(tr("Loading sismalp signal values : error reading file %1.\n").arg(_file->name()) );
      return false;
    }
    if(nExponent==0) { // samples are signed
      double maxMant=pow(2.0, (double) (nMantisse-1))-1.0;
      for(int i=0; i < _nSamples; i++) {
        double mant=(double) (maskM & val[ i ]);
        if(mant > maxMant) mant -= 2.0 * (maxMant + 1.0);
        samples[ i ]=mant * conversionFactor;
      }
    } else {
      for(int i=0; i < _nSamples; i++) {
        uint exp=(maskE & val[ i ]) >> 8;
        double mant=(double) (maskM & val[ i ]);
        mant *= pow(2.0, (double) exp);
        samples[ i ]=mant * conversionFactor;
      }
    }
    delete [] val;
  }
  fclose(f);
  return true;
}

/*!
  3 byte data format from Syscom: valid for XMR
*/
bool Signal::loadSyscom3Bytes(double * samples) const
{
  TRACE;
  FILE * f;
  if(!(f=fopen(_file->name().toLatin1().data(), "rb"))) {
    App::log(tr("Loading Syscom XMR signal values : Unable to open file %1\n").arg(_file->name()) );
    return false;
  }
  unsigned int bufLen=9*(_nSamples-1)+3;
  char * buf=new char[bufLen];
  char * bufPtr=buf;
  fseek(f, _offsetInFile, SEEK_SET);
  if(fread(buf, sizeof(char), bufLen, f)!=bufLen) {
    delete [] buf;
    return false;
  }
  double conversionFactor=unitPerCount();
  int val;
  for(int i=0; i<_nSamples; i++, bufPtr+=9) {
    val=*((qint32*) bufPtr) & 0x00FFFFFF;
    if(bufPtr[2] & 0x80) {
      ((char *)&val)[3]=0xFF;
      samples[i]=val*conversionFactor;
    } else {
      samples[i]=val*conversionFactor;
    }
  }
  delete [] buf;
  return true;
}

/*!
  2 byte data format from Syscom: valid for SMR, VMR and VMX
*/
bool Signal::loadSyscom2Bytes(double * samples) const
{
  TRACE;
  FILE * f;
  if(!(f=fopen(_file->name().toLatin1().data(), "rb"))) {
    App::log(tr("Loading Syscom SMR/VMR/VMX signal values : Unable to open file %1\n").arg(_file->name()) );
    return false;
  }
  unsigned int bufLen=6*(_nSamples-1)+2;
  char * buf=new char [bufLen];
  char * bufPtr=buf;
  fseek(f, _offsetInFile, SEEK_SET);
  if(fread(buf, sizeof(char), bufLen, f)!=bufLen) {
    delete [] buf;
    return false;
  }
  double conversionFactor=unitPerCount();
  for(int i=0; i<_nSamples; i++, bufPtr+=6) {
    samples[i]=*((qint16*)bufPtr)*conversionFactor;
  }
  delete [] buf;
  return true;
}

bool Signal::loadGuralpGcf(double * samples) const
{
  TRACE;
  QFile f(_file->name());
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(tr("Loading Guralp GCF values: unable to open file %1\n").arg(_file->name()) );
    return false;
  }
  const GuralpRecords& records=metaData<GuralpRecords>();
  int n=records.count(0);
  double sampFreq=samplingFrequency();
  double conversionFactor=unitPerCount();
  GuralpCompressedBlock b;
  for(int i=0; i<n; i++) {
    f.seek(records.pos(i));
    if(!b.readHeader(f)) {
      App::log(tr("Loading Guralp GCF values: error reading header at block %1/%2 in file %3 at offset %4.\n")
                       .arg(i).arg(n).arg(_file->name()).arg(records.pos(i), 8, 16, QChar(' ')));
      return false;
    }
    if(!b.readBody(f)) {
      App::log(tr("Loading Guralp GCF values: error reading body at block %1/%2 in file %3 at offset %4.\n")
                      .arg(i).arg(n).arg(_file->name()).arg(records.pos(i), 8, 16, QChar(' ')));
      return false;
    }
    int dIndex=qRound(_startTime.secondsTo(b.startTime())*sampFreq);
    int startIndex=dIndex;
    int endIndex=startIndex+b.nSamples();
    if(startIndex<0) startIndex=0;
    if(endIndex>_nSamples) endIndex=_nSamples;
    for(int j=startIndex; j<endIndex; j++) {
      samples[ j ]=b.sample(j-dIndex) * conversionFactor;
    }
  }
  return true;
}

bool Signal::loadMiniSeed(double * samples) const
{
  TRACE;
  double conversionFactor=unitPerCount();
  MiniSeedReader ms(_file->name(), &metaData<MiniSeedRecords>());
  if(!ms.isValid()) {
    return false;
  }
  void * recordSamples=nullptr;
  MiniSeedReader::Type sampleType=MiniSeedReader::Integer;
  int nRecordSamples=0;
  int nTotalRecordSamples=0;
  if(ms.atEnd() && nSamples()>0) {
    App::log(tr("Loading MiniSeed values: record list is empty for file %1\n")
                     .arg(_file->name()));
    return false;
  }
  while((recordSamples=ms.nextRecord(sampleType, nRecordSamples))) {
    nTotalRecordSamples+=nRecordSamples;
    if(nTotalRecordSamples>nSamples()) {
      App::log(tr("Loading MiniSeed values: error reading record at position %1 in file %2\n"
                  "                         Number of samples in records greater than in geopsy vector.\n"
                  "                         It is likely that a wrong file is loaded.\n").arg(ms.lastPos()).arg(_file->name()));
      return false;
    }
    switch(sampleType) {
    case MiniSeedReader::Integer: {
        int * dptr=(int *)recordSamples;
        for(int j=0; j<nRecordSamples; j++) {
          samples[j]=dptr[j]*conversionFactor;
        }
      }
      break;
    case MiniSeedReader::Float: {
        float * dptr=(float *)recordSamples;
        for(int j=0; j<nRecordSamples; j++) {
          samples[j]=Number::toDouble(dptr[j])*conversionFactor;
        }
      }
      break;
    case MiniSeedReader::Double: {
        double * dptr=(double *)recordSamples;
        for(int j=0; j<nRecordSamples; j++) {
          samples[j]=dptr[j]*conversionFactor;
        }
      }
      break;
    case MiniSeedReader::Text:
      App::log(tr("Loading MiniSeed values : bad sample type\n") );
      return false;
    }
    samples+=nRecordSamples;
  }
  if(nTotalRecordSamples<nSamples()) {
    App::log(tr("Loading MiniSeed values: not all samples were read from file %1\n")
                     .arg(_file->name()));
  }
  if(!ms.atEnd()) {
    App::log(tr("Loading MiniSeed values: error reading record at position %1 in file %2\n")
                     .arg(ms.lastPos()).arg(_file->name()));
    return false;
  }
  return true;
}

bool Signal::loadAsciiOneColumn(double * samples) const
{
  TRACE;
  FILE *fpin;
  if((fpin=fopen(_file->name().toLatin1().data(), "r"))==nullptr) {
    App::log(tr("Loading ASCII one column values: Unable to open file %1\n").arg(_file->name()) );
    return false;
  }
  int lineLen=1024;
  char * line=new char[ lineLen ];
  fseek(fpin, _offsetInFile, SEEK_SET);
  int i=0;
  double conversionFactor=unitPerCount();
  while(File::readLine(line, lineLen, fpin)!=0 && i < _nSamples) {
    sscanf(line ,"%lf", samples+i);
    samples[i] *= conversionFactor;
    i++;
  }
  fclose(fpin);
  delete [] line;
  return true;
}

bool Signal::loadAscii(double * samples, const QString& title) const
{
  TRACE;
  QFileInfo fi(_file->name());
  QString completeTitle=title.arg(fi.fileName());
  QFile f(_file->name());
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(completeTitle+": "+tr("Unable to open file\n"));
    return false;
  }
  f.seek(_offsetInFile);
  QByteArray buffer=f.readAll();

  // Read values in file
  int ns=nSamples();
  int iSig=numberInFile();
  double conversionFactor=unitPerCount();
  AsciiLineParser p(&buffer);
  const SignalFileFormat& format=file()->format();
  bool hexa=false;
  if(format.customFormat()) {
    const AsciiSignalFormat * f=static_cast<const AsciiSignalFormat *>(format.customFormat());
    p.setSeparators(f->separators());
    p.setEmptyValues(f->emptyValues());
    p.setQuotes(f->quotes());
    if(f->hexadecimal()) {
      hexa=true;
    }
  }
  int progressStep=ns >> 7;
  if(progressStep==0) {
    progressStep=ns;
  }
  int nextProgress=progressStep;
  GeopsyCoreEngine::instance()->setProgressMaximum(_db, ns);
  int is=0;
  if(hexa) { // Avoid testing for hexa format at each conversion
    while(is<ns) {
      while(is<nextProgress) {
        if(p.readColumn(iSig)) {
          samples[is]=p.hexaToInt()*conversionFactor;
        } else {
          App::log(completeTitle+": "
                   +tr("Reaching the end of file at sample %1 or missing samples\n").arg(is));
          is=ns;
          break;
        }
        is++;
      }
      GeopsyCoreEngine::instance()->setProgressValue(_db, is);
      nextProgress+=progressStep;
      if(nextProgress>ns) {
        nextProgress=ns;
      }
    }
  } else {
    while(is<ns) {
      while(is<nextProgress) {
        if(p.readColumn(iSig)) {
          samples[is]=p.toDouble()*conversionFactor;
        } else {
          App::log(completeTitle+": "
                   +tr("Reaching the end of file at sample %1 or missing samples").arg(is));
          is=ns;
          break;
        }
        is++;
      }
      GeopsyCoreEngine::instance()->setProgressValue(_db, is);
      nextProgress+=progressStep;
      if(nextProgress>ns) {
        nextProgress=ns;
      }
    }
  }
  if(nextProgress<ns) {  // Make sure that progress finishes.
    GeopsyCoreEngine::instance()->setProgressValue(_db, ns);
  }
  return true;
}

bool Signal::loadFourier(double * samples) const
{
  TRACE;
  static const QString title(tr("Loading Fourier spectrum values (%1)"));
  QFileInfo fi(_file->name());
  QString completeTitle=title.arg(fi.fileName());
  QFile f(_file->name());
  if(!f.open(QIODevice::ReadOnly)) {
    App::log(completeTitle+": "+tr("Unable to open file\n"));
    return false;
  }
  QByteArray buffer=f.readAll();

  // Read values in file
  int ns=nSamples();
  int iSig=numberInFile();
  AsciiLineParser p(&buffer, 3);
  int progressStep=ns >> 7;
  if(progressStep==0) {
    progressStep=ns;
  }
  int nextProgress=progressStep;
  GeopsyCoreEngine::instance()->setProgressMaximum(_db, ns);
  int is=0;
  int columns[3];
  columns[0]=0;
  columns[1]=2*iSig+1;
  columns[2]=columns[1]+1;
  double df=1.0/duration();
  for(int i=0; i<ns; i++) {
    samples[i]=0.0;
  }
  while(is<ns) {
    while(is<nextProgress) {
      if(!p.readColumns(3, columns)) {
        App::log(completeTitle+": "
                 +tr("Reaching the end of file at sample %1 or missing samples").arg(is));
        is=ns;
        break;
      }
      int index=round(p.toDouble(0)/df);
      setComplex(samples, index, Complex(p.toDouble(1), p.toDouble(2)));
      is++;
    }
    GeopsyCoreEngine::instance()->setProgressValue(_db, is);
    nextProgress+=progressStep;
    if(nextProgress>ns) {
      nextProgress=ns;
    }
  }
  if(nextProgress<ns) {  // Make sure that progress finishes.
    GeopsyCoreEngine::instance()->setProgressValue(_db, ns);
  }
  return true;
}

bool Signal::writeError(const DoubleSignal * sig, QString logMessage) const
{
  TRACE;
  App::log(logMessage+"\n");
  restoreType(sig);
  return false;
}

bool Signal::writeSeg2Header(QFile& f, const QString& keyword,
                             const QString& sep, const QString& value,
                             const DoubleSignal * sig, qint16& blocksize) const
{
  QString str=keyword+sep+value+"\n";
  qint16 length=static_cast<qint16>(str.length()+2);
  if(f.write(reinterpret_cast<const char *>(&length), 2)!=2 ||
     f.write(str.toLatin1().data(), length-2)!=length-2) {
    return writeError(sig, tr("[SEG2] Error while writing %1").arg(keyword));
  }
  blocksize+=length;
  return true;
}

bool Signal::writeSeg2(QFile& f, double delay) const
{
  TRACE;
  const DoubleSignal * sig=saveType(Waveform);
  char buf[32];

  double factor=32767./maximumAmplitude();
  qint64 pos=f.pos();
  qint64 posEnd;

  qint16 blocksize=32;
  const QString errorMsg(tr("[SEG2] Error while writing %1"));

  buf[0]=0x44;
  buf[1]=0x22;
  *reinterpret_cast<long*>(buf+4)=nSamples()*4;
  *reinterpret_cast<long*>(buf+8)=nSamples();
  *reinterpret_cast<qint16*>(buf+12)=2;
  if(f.write(buf, 32)!=32) return writeError(sig, errorMsg.arg("header"));

  writeSeg2Header(f, "DELAY", " ", QString::number(delay, 'g', 6), sig, blocksize);
  writeSeg2Header(f, "SAMPLE_INTERVAL", " ", QString::number(samplingPeriod(), 'g', 6), sig, blocksize);
  writeSeg2Header(f, "DESCALING_FACTOR", " ", QString::number(1./factor, 'g', 6), sig, blocksize);
  writeSeg2Header(f, "STACK", " ", QString::number(metaData<StackCount>().value()), sig, blocksize);
  Point src=source();
  writeSeg2Header(f, "SOURCE_LOCATION", " ", QString("%1 %2 %3").arg(src.x(), 0, 'f', 6)
                                                                .arg(src.y(), 0, 'f', 6)
                                                                .arg(src.z(), 0, 'f', 6), sig, blocksize);
  writeSeg2Header(f, "RECEIVER_LOCATION", " ", QString("%1 %2 %3").arg(_receiver.x(), 0, 'f', 6)
                                                             .arg(_receiver.y(), 0, 'f', 6)
                                                             .arg(_receiver.z(), 0, 'f', 6), sig, blocksize);
  QStringList picks=metaData<TimePick>().names();
  for(QStringList::iterator it=picks.begin(); it!=picks.end(); it++) {
    QString pn=*it;
    DateTime p=metaData<TimePick>().value(pn);
    if(p.isValid()) {
      pn.replace(" ", "_");
      if(pn.isEmpty()) {
        pn="UNAMED";
      }
      writeSeg2Header(f, "ARRIVAL_TIME "+pn, " ", QString::number(startTime().secondsTo(p)+delay, 'g', 6), sig, blocksize);
    }
  }
  writeSeg2Header(f, "RAW_RECORD", " ", metaData<OriginalFileName>().value(), sig, blocksize);
  writeSeg2Header(f, "NAME", "\n", _name, sig, blocksize);

  int rem4, val;
  qint16 length=-1;
  if(f.write(reinterpret_cast<const char*>(&length), 2)!=2)
    return writeError(sig, errorMsg.arg("end of signal header (length)"));
  blocksize+=2;
  rem4=blocksize % 4;
  if(rem4>0) {
    if(f.write(buf, rem4)!=rem4) {
      return writeError(sig, errorMsg.arg("alignment of signal header(%1)").arg(rem4));
    }
    blocksize+=rem4;
  }
  CONST_LOCK_SAMPLES(double, sigSamples, sig)
    for(int i=0; i<_nSamples; i++) {
      val=static_cast<int>(round(sigSamples[i]*factor));
      if(f.write(reinterpret_cast<const char*>(&val), 4)!=4) {
        return writeError(sig, errorMsg.arg("signal sample(%1)").arg(i));
      }
    }
  UNLOCK_SAMPLES(sig);
  posEnd=f.pos();
  f.seek(pos + 2);
  if(f.write(reinterpret_cast<const char*>(&blocksize), 2)!=2)
  return writeError(sig, errorMsg.arg("signal block size"));
  f.seek(posEnd);

  restoreType(sig);
  return true;
}

bool Signal::writeSac(QDataStream& s) const
{
  TRACE;
  const DoubleSignal * sig=saveType(Waveform);

  static const QString errorMsg(tr("[SAC] Error while writing %1"));

  SACHeader h;
  h.floats.field.USER7=static_cast<float>(receiver().x());
  h.floats.field.USER8=static_cast<float>(receiver().y());
  h.floats.field.USER9=static_cast<float>(receiver().z());
  switch (amplitudeUnit()) {
  case Displacement: h.ints.field.IDEP=6; break;
  case Velocity: h.ints.field.IDEP=7; break;
  case Acceleration: h.ints.field.IDEP=8; break;
  default: h.ints.field.IDEP=5; break;
  }
  h.ints.field.NVHDR=6;  // SAC version 6 compatible (2002)
  h.ints.field.IFTYPE=1; // iftype is time series
  h.bools.field.LEVEN=0xffffffff; // leven is true
  h.ints.field.NPTS=nSamples();
  h.floats.field.DELTA=static_cast<float>(samplingPeriod());
  h.floats.field.SCALE=static_cast<float>(countPerVolt());
  if(name().length()>8) {
    strncpy(h.chars.field.KSTNM, name().toLatin1().data(), 8);
  } else {
    strncpy(h.chars.field.KSTNM, name().toLatin1().data(), static_cast<size_t>(name().length()));
  }
  strncpy(h.chars.field.KCMPNM, componentLetter(component()).toLatin1().data(), 1);
  h.ints.field.NZYEAR=_startTime.date().year();
  h.ints.field.NZJDAY=static_cast<qint32>(QDate(h.ints.field.NZYEAR, 1, 1).daysTo(_startTime.date())+1);
  h.ints.field.NZHOUR=_startTime.hour();
  h.ints.field.NZMIN=_startTime.minute();
  h.ints.field.NZSEC=_startTime.second();
  h.ints.field.NZMSEC=qRound(_startTime.fractions()*1e3);
  h.floats.field.B=static_cast<float>(_startTime.fractions()-h.ints.field.NZMSEC*1e-3);
  // Picks: SAC can store only ten without names
  //        save only pick named SAC_0 to SAC_9
  bool hasSacPick=false;
  const TimePick& tp=metaData<TimePick>();
  if(tp.count()>0) {
    for(int i=0;i<10;i++) {
      QString pn="SAC_"+QString::number(i);
      if(TimePick::registered(pn) && tp.hasIndex(0, pn)) {
        h.floats.field.T[i]=static_cast<float>(startTime().secondsTo(metaData<TimePick>().value(pn)));
        hasSacPick=true;
      }
    }
    if(!hasSacPick) {
      App::log(tr("SAC header can store only 10 unamed time picks.\n"
                  "Only picks named 'SAC_0' to 'SAC_9'\n"));
    }
  }
  if(!h.write(s)) return writeError(sig, errorMsg.arg(tr("header")));
  qint64 offset=s.device()->pos();
  CONST_LOCK_SAMPLES(double, sigSamples, sig)
    float val;
    double conversionFactor=countPerUnit();
    for(int i=0; i<_nSamples; i++) {
      val=static_cast<float>(sigSamples[i]*conversionFactor);
      s << val;
    }
  UNLOCK_SAMPLES(sig);
  if(s.device()->pos()-offset!=_nSamples*static_cast<int>(sizeof(float))) {
    return writeError(sig, errorMsg.arg(tr("samples")));
  }
  restoreType(sig);
  return true;
}

bool Signal::writeGse(QFile& f) const
{
  TRACE;
  const DoubleSignal * sig=saveType(Waveform);

  static const QString errorMsg(tr("[GSE] Error while writing %1"));

  // Build header line
  // Index 0
  QString header("WID2 ");
  // Index 6
  header+=_startTime.toString("yyyy/MM/dd hh:mm:ssz").left(23)+" ";
  // Index 30
  header+=name().leftJustified(5, QChar(' '), true)+" ";
  /* See http://www.iris.edu/manuals/seed/SEEDManual_V2.4_Appendix-A.pdf
     for FDSN channel codes
     Geopsy always export in HH, which means High Broad Band with high gain.
  */
  // Index 36
  switch (component()) {
  case Vertical:  header+="HHZ      "; break;
  case North:     header+="HHN      "; break;
  case East:      header+="HHE      "; break;
  default:        header+="         "; break;
  }
  // Index 45
  header+="CM6 ";
  // Index 49
  header+=QString::number(nSamples()).leftJustified(8, QChar(' '), true)+" ";
  // Index 58
  header+=QString::number(1.0/samplingPeriod()).leftJustified(11, QChar(' '), true)+" ";
  // Index 70: calibration factor at calper
  // Maximum number that can be used in the GSE compression algorithm?
  // Tried INT_MAX, but unstable
  // Tried 0x1FFFFFFF, but still unstable
  double factor=0x0FFFFFFF;
  double max=maximumAmplitude();
  if(max!=0.0) {
    factor/=maximumAmplitude();
  }
  header+=QString::number(1e9/factor).leftJustified(10, QChar(' '), true)+" ";
  // Index 81: calibration period
  header+="  1.000 ";
  // Index 89: Instrument type
  header+="  NONE ";
  // Index 96: horizontal orientation of sensor (from north)
  // Index 102: vertical horientation
  switch(component()) {
  case Vertical:
    header+=" -1.0 ";
    header+=" 0.0 ";
    break;
  default:
    header+="  0.0 ";
    header+="90.0 ";
    break;
  }
  header+="\n";
  header+="DAT2\n";
  if(f.write(header.toLatin1().data(), header.count())!=header.count())
    return writeError(sig, errorMsg.arg(tr("header")));
  // Samples
  int iComp=0,nComp=0;
  CONST_LOCK_SAMPLES(double, sigSamples, sig)
    int * decomp=new int[_nSamples];
    for(int i=0; i<_nSamples; i++) {
      decomp[i]=static_cast<int>(round(sigSamples[i]*factor));
    }
    int cs=Gse::checksum(decomp, _nSamples);
    Gse::diff(decomp, _nSamples);
    Gse::diff(decomp, _nSamples);
    char * comp=Gse::compress6(_nSamples, decomp, nComp);
    nComp-=80; // Force last line to be written after loop
    for(iComp=0;iComp<nComp;iComp+=80) {
      if(f.write(comp+iComp,80)!=80 || f.write("\n")!=1) {
        return writeError(sig, errorMsg.arg(tr("compressed samples")));
      }
    }
    nComp+=80; // Recover orignal nComp
    // Write beginning of last line
    nComp-=iComp; // Get the number of chars of last line
    if(f.write(comp+iComp,nComp)!=nComp) {
      return writeError(sig, errorMsg.arg(tr("compressed samples (last line)")));
    }
    // Fill the rest with spaces
    for(;nComp<80; nComp++) {
      if(f.write(" ", 1)!=1) {
        return writeError(sig, errorMsg.arg(tr("compressed samples (last line)")));
      }
    }
    // Write checksum
    header="\nCHK2 ";
    header+=QString::number(cs);
    header+="\n";
    if(f.write(header.toLatin1().data(),header.count())!=header.count()) {
      return writeError(sig, errorMsg.arg(tr("check sum")));
    }
    delete [] comp;
    delete [] decomp;
  UNLOCK_SAMPLES(sig);
  restoreType(sig);
  return true;
}

void Signal::miniSeedRecordHandler (char *record, int reclen, void *f)
{
  if(static_cast<QFile*>(f)->write(record, reclen)!=reclen) {
    App::log(tr("Cannot write to mseed output file\n") );
  }
}

bool Signal::writeMiniSeed(QFile& f) const
{
//#define MINISEED_EXPORT_INTEGER
  TRACE;
  LibMSeed::MSRecord * msr=LibMSeed::msr_init(nullptr);
  msr->reclen=512; // Always output 512 byte records compatible with seiscomp mseedscan plugin
#ifdef MINISEED_EXPORT_INTEGER
  msr->encoding=DE_STEIM1;
  msr->sampletype='i';
#else
  msr->encoding=DE_FLOAT32;
  msr->sampletype='f';
#endif
  msr->numsamples=_nSamples;
  msr->byteorder=1; // Big endian
  msr->dataquality= 'D';
  if(name().indexOf("_")==2 && name().count()<=8) {
    strcpy(msr->network, name().left(2).toLatin1().data());
    strcpy(msr->station, name().mid(3, 5).toLatin1().data());
  } else { // Non standard signal names
    App::log(tr("Non standard signal name (NN_SSSSS), skip network code (NN)\n") );
    strcpy(msr->network, "");
    strcpy(msr->station, name().left(11).toLatin1().data());
  }
  strcpy(msr->location, "");
  // Set start time
  LibMSeed::BTime t;
  t.year=static_cast<quint16>(_startTime.date().year());
  t.day=static_cast<quint16>(QDate(t.year, 1,1).daysTo(_startTime.date())+1);
  t.hour=static_cast<quint8>(_startTime.hour());
  t.min=static_cast<quint8>(_startTime.minute());
  t.sec=static_cast<quint8>(_startTime.second());
  t.fract=static_cast<quint16>(_startTime.fractions()*1e4);
  msr->starttime=LibMSeed::ms_btime2hptime(&t);
  switch (component()) {
  case All:
  case Horizontal:
  case UndefinedComponent:
  case Ignore:
  case Time:
  case Vertical:
    strcpy(msr->channel, "BHZ");
    break;
  case North:
    strcpy(msr->channel, "BHN");
    break;
  case East:
    strcpy(msr->channel, "BHE");
    break;
  }
  msr->samprate=samplingFrequency();
  int64_t packedSamples;
#ifdef MINISEED_EXPORT_INTEGER
  int * samples=new int[_nSamples];
#else
  float * samples=new float[_nSamples];
#endif
  msr->datasamples=samples;
  CONST_LOCK_SAMPLES(double, thisSamples, this)
    double factor=countPerUnit();
    for (int i=0; i<_nSamples; i++) {
#ifdef MINISEED_EXPORT_INTEGER
      samples[i]=static_cast<int>(round(thisSamples[i]*factor));
#else
      samples[i]=static_cast<float>(thisSamples[i]*factor);
#endif
    }
  UNLOCK_SAMPLES(this)
  msr_pack(msr, &miniSeedRecordHandler, &f, &packedSamples, 1, 0);
  msr->datasamples=nullptr;
  delete [] samples;
  msr_free(&msr);
  return packedSamples==_nSamples;
}

bool Signal::writeAscii(QFile& f, int numInFile) const
{
  TRACE;
  QByteArray buf;
  buf+="# channel ";
  buf+=QByteArray::number(numInFile+1);
  buf+=" ";
  buf+=nameComponent();
  buf+="\n";
  if(f.write(buf)!=buf.count()) return false;
  CONST_LOCK_SAMPLES(double, thisSamples, this)
    if(isReal()) {
      for(int i=0; i < _nSamples; i ++) {
        buf.clear();
        buf+=QByteArray::number(thisSamples[ i ], 'g', 15);
        buf+="\n";
        if(f.write(buf)!=buf.count()) return false;
      }
    } else if(isComplex()) {
      double fNyquist=0.5/_samplingPeriod;
      int nSamples2=_nSamples >> 1;
      double df=fNyquist/static_cast<double>(nSamples2);
      for(int i=1; i<=nSamples2; i++) {
        buf.clear();
        buf+=QByteArray::number(df*static_cast<double>(i), 'g', 15);
        buf+="\t";
        buf+=QByteArray::number(amplitude(thisSamples, i), 'g', 15);
        buf+="\t";
        buf+=QByteArray::number(phase(thisSamples, i), 'g', 15);
        buf+="\n";
        if(f.write(buf)!=buf.count()) return false;
      }
    }
  UNLOCK_SAMPLES(this)
  return true;
}

/*!
  Writes this signal as a SEGY or SU trace with IEEE floating point.
  For SU export, sets \a su to true.
*/
bool Signal::writeSegySu(QDataStream& s, int indexInFile, bool su) const
{
  TRACE;
  const DoubleSignal * sig=saveType(Waveform);

  SEGYTraceHeader h;

  h.field.traceNumberInLine=indexInFile+1;
  h.field.traceNumberInFile=indexInFile+1;
  QString originalFileName=metaData<OriginalFileName>().value();
  if(originalFileName.contains(QRegExp("[0-9]{4}\\.[a-zA-Z0-9]{3}$"))) {
    h.field.originalRecordNumber=originalFileName.right(8).left(4).toInt();
  } else {
    h.field.originalRecordNumber=1;
  }
  h.field.traceNumberInRecord=_numberInFile+1;
  h.field.traceIndentificationCode=1;
  h.field.numberSumVertical=static_cast<qint16>(metaData<StackCount>().value());
  h.field.numberStackHorizontal=1;
  h.field.dataUse=1; // Production data
  if(!h.setNSamples(nSamples())) {
     return false;
  }
  h.field.sampleInterval=static_cast<quint16>(round(1.e+6*samplingPeriod()));
  Point src=source();
  // Calculate scale factor to apply on coordinates and elevations
  if(!h.setCoordinateFactor(_receiver, src)) {
    return false;
  }
  if(!h.setElevationFactor(_receiver, src)) {
    return false;
  }
  h.setReceiver(_receiver);
  h.setSource(src);

  h.setStartTime(_startTime, su);
  h.field.timeBase=4;   // UTC time base
  h.field.gainType=1;   // Fixed gain
  h.field.correlated=1; // Not correlated

  h.write(s);
  ASSERT(sizeof(float)==4);
  CONST_LOCK_SAMPLES(double, sigSamples, sig)
    double conversionFactor=countPerUnit();
    for(int i=0; i<_nSamples; i++) {
      s << static_cast<float>(sigSamples[i]*conversionFactor);
    }
  UNLOCK_SAMPLES(this)

  restoreType(sig);
  return true;
}

/*!
  Read header properties from stream for database \a db (version 2).
  For compatibility purpose.
*/
bool Signal::read(FILE * f, SignalDatabase * db)
{
  TRACE;
  int bufLen=256;
  char * buf=new char [ bufLen ];
  File::readLine(buf, bufLen, f);
  if(strncmp(buf, "ID=", 3)==0) setId(atoi(buf + 3));
  else return readError(buf);
  File::readLine(buf, bufLen, f);
  char * lastBufChar=buf + strlen(buf)-1;
  if(*lastBufChar=='\n') *lastBufChar='\0';
  if(strncmp(buf, "NAME=", 5)==0) setName(buf + 5);
  else return readError(buf);
  File::readLine(buf, bufLen, f);
  if(strncmp(buf, "COMPONENT=", 10)==0) {
    int comp=atoi(buf + 10);
    // Due to historical reasons the number in the sdb file is not the same
    // as the number in the Signal structure
    switch (comp) {
    case 0:
      setComponent(Signal::Vertical);
      break;
    case 2:
      setComponent(Signal::North);
      break;
    case 1:
      setComponent(Signal::East);
      break;
    case 3:
      setComponent(Signal::UndefinedComponent);
      break;
    case 4:
      setComponent(Signal::Horizontal);
      break;
    case 5:
      setComponent(Signal::All);
      break;
    }
  } else return readError(buf);
  File::readLine(buf, bufLen, f);
  double secondsFromReference;
  DateTime reference;
  if(strncmp(buf, "T0=", 3)==0) secondsFromReference=atof(buf+3);
  else return readError(buf);
  File::readLine(buf, bufLen, f);
  if(strncmp(buf, "TIME REFERENCE=", 15)==0 || strncmp(buf, "TIME DATUM=", 11)==0) {
    char * valuePtr=buf;
    if(valuePtr[6]=='R') valuePtr+=15; else valuePtr+=11;
    if(strlen(valuePtr) > 0) {
      QTime t;
      QDate d;
      int dd=1, MM=1, yyyy=2004, hh=0, mm=0, ss=0;
      char * tmpPtr=strtok(valuePtr, "/");
      if(tmpPtr) dd=atoi(tmpPtr);
      tmpPtr=strtok(nullptr, "/");
      if(tmpPtr) MM=atoi(tmpPtr);
      tmpPtr=strtok(nullptr, " ");
      if(tmpPtr) yyyy=atoi(tmpPtr);
      d.setDate(yyyy, MM, dd);
      tmpPtr=strtok(nullptr, ":");
      if(tmpPtr) hh=atoi(tmpPtr);
      tmpPtr=strtok(nullptr, ":");
      if(tmpPtr) {
        mm=atoi(tmpPtr);
        ss=atoi(tmpPtr + strlen(tmpPtr) + 1);
      }
      t.setHMS(hh, mm, ss);
      if(t.isValid() && d.isValid()) {
        reference=DateTime(QDateTime(d, t));
        DateTime startTime=reference;
        startTime.addSeconds(secondsFromReference);
        setStartTime(startTime);
      }
    }
  } else return readError(buf);
  File::readLine(buf, bufLen, f);
  if(strncmp(buf, "DELTA T=", 8)==0) setSamplingPeriod(atof(buf + 8));
  else return readError(buf);
  // Transformation to complex is delayed when the file will be loaded
  File::readLine(buf, bufLen, f);
  if(strncmp(buf, "TYPE=", 5)==0)
    setType((DoubleSignal::SignalType) atoi(buf + 5));
  else return readError(buf);
  File::readLine(buf, bufLen, f);
  // Compatibility with version 2 is ensured as PICK ID will also be read in this loop
  while(strncmp(buf, "PICK ", 4)==0) {
    double val;
    int i;
    sscanf(buf + 4, "%i=%lg", &i, &val);
    if(i>=1 && i<=10) {          // Version 2 accept only pick from 1 to 10
      TimePick& tp=beginModifyMetaData<TimePick>();
      tp.setValue(QString::number(i-1), startTime().shifted(val));
      endModifyMetaData(tp);
    }
    File::readLine(buf, bufLen, f);
  }
  if(strncmp(buf, "RECEIVER=", 9)==0) {
    double x, y, z;
    sscanf(buf + 9, "%lg %lg %lg", &x, &y, &z);
    _receiver.setX(x);
    _receiver.setY(y);
    _receiver.setZ(z);
  } else return readError(buf);
  File::readLine(buf, bufLen, f);
  if(strncmp(buf, "SOURCE=", 7)==0) {
    double x, y, z;
    sscanf(buf + 7, "%lg %lg %lg", &x, &y, &z);
    SeismicEvent e;
    e.setPosition(Point(x, y, z));
    e.setTime(startTime());
    e.setType(SeismicEvent::Hammer);
    e.setForce(Point(0.0, 0.0, -1.0));
    _db->seismicEvents()->add(e);
  } else return readError(buf);

  if(db->version()>=3) {
    File::readLine(buf, bufLen, f);
    if(strncmp(buf, "N SAMPLES=", 10)==0)
      sscanf(buf + 10, "%i", &_nSamples);
    else return readError(buf);
    setNSamples(_nSamples);
    File::readLine(buf, bufLen, f);
    if(strncmp(buf, "N SAMPLES IN FILE=", 18)!=0) return readError(buf);
    File::readLine(buf, bufLen, f);
    if(strncmp(buf, "NUMBER IN FILE=", 15)==0)
      sscanf(buf + 15, "%i", &_numberInFile);
    else return readError(buf);
    File::readLine(buf, bufLen, f);
    if(strncmp(buf, "OFFSET IN FILE=", 15)==0) {
      long tmp;
      sscanf(buf + 15, "%li", &tmp);
      _offsetInFile=tmp;
  } else return readError(buf);
    File::readLine(buf, bufLen, f);
    if(strncmp(buf, "BYTE INCREMENT=", 15)==0)
      sscanf(buf + 15, "%i", &_byteIncrement);
    else return readError(buf);
    File::readLine(buf, bufLen, f);
    if(strncmp(buf, "CHECKSUM=", 9)!=0) return readError(buf); // not stored anymore
    File::readLine(buf, bufLen, f);
    if(strncmp(buf, "COUNT PER VOLT=", 15)==0)
      sscanf(buf + 15, "%lg", &_countPerVolt);
    else return readError(buf);
    File::readLine(buf, bufLen, f);
    if(strncmp(buf, "VOLT PER UNIT=", 14)==0)
      sscanf(buf + 14, "%lg", &_voltPerUnit);
    else return readError(buf);
    File::readLine(buf, bufLen, f);
    if(strncmp(buf, "AMPLITUDE UNIT=", 15)==0) {
      int tmp;
      sscanf(buf + 15, "%i", &tmp);
      if(tmp>=UndefinedUnit && tmp<=Acceleration)
        _amplitudeUnit=static_cast<AmplitudeUnits>(tmp);
    } else return readError(buf);
  }
  setHeaderModified(false);
  delete [] buf;
  return true;
}

void Signal::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  writeProperty(s, "ID", id());
  writeProperty(s, "Name", name());
  writeProperty(s, "Component", componentStandardName());
  writeProperty(s, "StartTime", startTime().toString());
  writeProperty(s, "SamplingPeriod", samplingPeriod());
  writeProperty(s, "Type", typeString(type()));
  writeProperty(s, "NSamples", nSamples());
  writeProperty(s, "CountPerVolt", countPerVolt());
  writeProperty(s, "VoltPerUnit", voltPerUnit());
  writeProperty(s, "AmplitudeUnit", amplitudeUnitStandardName());
  writeProperty(s, "NumberInFile", numberInFile());
  writeProperty(s, "Receiver", receiver().toString(15));
  writeProperty(s, "UtmZone", _utmZone.toString());
  writeProperty(s, "OffsetInFile", offsetInFile());
  writeProperty(s, "ByteIncrement", byteIncrement());
  writeProperty(s, "TimeRange", _timeRange.toString());
  // Optional data
  int n=_optionalData.count();
  const SortedVector<int, MetaData>& v=_optionalData.vector();
  for(int i=0; i<n; i++) {
    const MetaData * d=v.at(i);
    if(d->isStored()) {
      if(d->referenceCount()>1) {
        d->xml_writeLink(s);
      } else if(d->storeAsProperty()) {
        XMLSaveAttributes att;
        d->xml_attributes(att, context);
        d->writeProperties(s, att);
      }
    }
  }
}

void Signal::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  // Optional meta data (only if not shared)
  int n=_optionalData.count();
  const SortedVector<int, MetaData>& v=_optionalData.vector();
  for(int i=0; i<n; i++) {
    const MetaData * d=v.at(i);
    if(d->isStored() && !d->storeAsProperty() && d->referenceCount()==1) {
      XMLSaveAttributes att;
      d->xml_attributes(att, context);
      d->xml_save(s, context, att);
    }
  }
}

XMLMember Signal::xml_member(XML_MEMBER_ARGS)
{
  Q_UNUSED(context)
  // First look for standard members
  if(tag.size()>=2) {
    switch (tag[0].unicode()) {
    case 'A':
      if(tag=="AmplitudeUnit") return XMLMember(11);
      break;
    case 'B':
      if(tag=="ByteIncrement") return XMLMember(15);
      break;
    case 'b': // Special tag handled by XMLParser directly, avoid meta data queries
      if(tag=="binDataFile") return XMLMember(XMLMember::Unknown);
      break;
    case 'C':
      if(tag.size()>=4) {
        switch (tag[3].unicode()) {
        case 'c':
          if(tag=="CheckSum") return XMLMember(16);
          break;
        case 'n':
          if(tag=="CountPerVolt") return XMLMember(9);
          break;
        case 'p':
          if(tag=="Component") return XMLMember(2);
          break;
        default:
          break;
        }
      }
      break;
    case 'D':
      if(tag=="DeltaT") return XMLMember(5);    // For compatibility only
      break;
    case 'I':
      if(tag=="ID") return XMLMember(0);
      break;
    case 'N':
      switch (tag[1].unicode()) {
      case 'a':
        if(tag=="Name") return XMLMember(1);
        break;
      case 'S':
        if(tag=="NSamples") return XMLMember(7);
        break;
      case 'u':
        if(tag=="NumberInFile") return XMLMember(8);
        break;
      default:
        break;
      }
      break;
    case 'O':
      if(tag=="OffsetInFile") return XMLMember(14);
      break;
    case 'R':
      if(tag=="Receiver") return XMLMember(12);
      break;
    case 'S':
      switch (tag[1].unicode()) {
      case 'a':
        if(tag=="SamplingPeriod") return XMLMember(5);
        break;
      case 'o':
        if(tag=="Source") return XMLMember(13);  // Obsolete, kept for compatibility
        break;
      case 't':
        if(tag=="StartTime") return XMLMember(3);
        break;
      }
      break;
    case 'T':
      switch (tag[1].unicode()) {
      case '0':
        if(tag=="T0") return XMLMember(19);  // Obsolete, kept for compatibility with version <=7
        break;
      case 'i':
        if(tag=="TimeReference") return XMLMember(4);  // Obsolete, kept for compatibility
        else if(tag=="TimeRange") return XMLMember(18);
        else if(tag=="TimePick") {
          if(_db->version()>=8) {
            return XMLMember(100);
          } else {
            return XMLMember(20);  // Skip classical metadata processing
          }
        }
        break;
      case 'y':
        if(tag=="Type") return XMLMember(6);
        break;
      default:
        break;
      }
      break;
    case 'U':
      if(tag=="UtmZone") return XMLMember(17);
      break;
    case 'V':
      if(tag=="VoltPerUnit") return XMLMember(10);
      break;
    default:
      break;
    }
  }
  // Not a standard data name, look for non-standard ones...
  static QString sharedAtt="sharedId";
  XMLRestoreAttributeIterator it=attributes.find(sharedAtt);
  if(it!=attributes.end()) { // It is a link to shared data
    MetaData * d=_db->resolveMetaData(it.value().toInt());
    if(d) {
      if(tag==d->xml_tagName()) {
        _optionalData.add(d);
        return XMLMember(101);
      } else {
        App::log(tr("Mismatch resolving shared meta data shared id %1: expecting type %2, found %3.\n")
            .arg(it.value().toInt()).arg(tag.toString()).arg(d->xml_tagName()));
      }
    }
  } else { // Not shared
    int id=MetaDataFactory::instance()->id(tag.toString());
    if(id>-1) {
      MetaData * d=_optionalData.data(id);
      if(!d->storeAsProperty()) {
        return XMLMember(d);
      } else {
        return XMLMember(100);
      }
    } else {
      App::log(tr("Unknown MetaData %1\n").arg(tag.toString()) );
    }
  }
  return XMLMember(XMLMember::Unknown);
}

bool Signal::xml_setProperty(XML_SETPROPERTY_ARGS)
{
  Q_UNUSED(context)
  bool ok=true;
  switch (memberID) {
  case 0:
    setId(content.toInt());
    return true;
  case 1:
    setName(content.toString());
    return true;
  case 2:
    setComponent(standardComponent(content.toString()));
    return true;
  case 3:
    setStartTime(content.toTime(DateTime::defaultFormat, &ok));
    return ok;
  case 4: { // Obsolete, kept for compatibility
      DateTime t=content.toTime("d/M/yyyy h:m:s", &ok);
      if(ok) {
        if(metaDataMap().hasData(CompatibilityTimeReference::staticId())) {
          setMetaData(CompatibilityTimeReference(t, metaData<CompatibilityTimeReference>().t0()));
        } else {
          setMetaData(CompatibilityTimeReference(t));
        }
      }
    }
    return ok;
  case 5:
    if(_db->version()<8) {
      if(metaDataMap().hasData(CompatibilityTimeReference::staticId())) {
        setStartTime(metaData<CompatibilityTimeReference>().startTime());
      } else {
        App::log(tr("Compatibility with old db format: no TimeReference and no T0 found.\n"));
        return false;
      }
    }
    setSamplingPeriod(content.toDouble());
    return true;
  case 6:
    setType(type(content.toString(), &ok));
    return ok;
  case 7:
    setNSamples(content.toInt(&ok));
    return ok;
  case 8:
    setNumberInFile (content.toInt(&ok));
    return ok;
  case 9:
    setCountPerVolt (content.toDouble(&ok));
    return ok;
  case 10:
    setVoltPerUnit (content.toDouble(&ok));
    return ok;
  case 11:
    setAmplitudeUnit(standardAmplitudeUnit(content.toString()));
    return true;
  case 12: {
      Point p;
      p.fromString(content);
      setReceiver(p);
      return true;
    }
  case 13: // Obsolete, kept for compatibility
    if(metaDataMap().hasData(CompatibilityTimeReference::staticId())) {
      const CompatibilityTimeReference& t=metaData<CompatibilityTimeReference>();
      SeismicEvent e;
      Point p;
      p.fromString(content);
      e.setPosition(p);
      e.setTime(t.timeReference());
      e.setType(SeismicEvent::Hammer);
      e.setForce(Point(0.0, 0.0, -1.0));
      _db->seismicEvents()->add(e);
      return true;
    } else {
      App::log(tr("Compatibility with obsolete TimeReference and T0: found no time reference."));
      return false;
    }
  case 14:
    setOffsetInFile(content.toLongLong(&ok));
    /* Recover of corrupted offsets due to qint32... 201701
      if(file()->format().id()==SignalFileFormat::GeopsySignal) {
      SignalFile * f=file();
      qint64 offset=16+8+4*4;
      SubSignalPool subpool;
      subpool.addFile(f);
      offset+=subpool.count()*sizeof(qint32);
      ASSERT(numberInFile()<subpool.count());
      for(int i=0; i<numberInFile(); i++) {
        offset+=subpool.at(i)->nSamples()*sizeof(double);
      }
      qDebug() << numberInFile() << offset;
      setOffsetInFile(offset);
    }*/
    return ok;
  case 15:
    setByteIncrement (content.toInt(&ok));
    return ok;
  case 16: // checksum, ignored, not stored anymore
    return true;
  case 17: {
      UtmZone z;
      z.fromString(content.toString());
      setUtmZone(z);
      return true;
    }
  case 18:
    if(_db->version()>=8) {
      SparseTimeRange sr;
      if(!sr.fromString(content.toString())) {
        return false;
      }
      setTimeRange(sr);
      return true;
    } else {         // Relative time range in version 7, kept for compatibility
      if(metaDataMap().hasData(CompatibilityTimeReference::staticId())) {
        const CompatibilityTimeReference& t=metaData<CompatibilityTimeReference>();
        SparseTimeRange sr;
        if(!sr.fromString(t.timeReference(), content.toString())) {
          return false;
        }
        setTimeRange(sr);
        return true;
      } else {
        App::log(tr("Compatibility with obsolete TimeReference and T0: found no time reference.\n"));
        return false;
      }
    }
  case 19: // Obsolete, kept for compatibility
    if(metaDataMap().hasData(CompatibilityTimeReference::staticId())) {
      setMetaData(CompatibilityTimeReference(metaData<CompatibilityTimeReference>().timeReference(),
                                             content.toDouble()));
    } else {
      setMetaData(CompatibilityTimeReference(content.toDouble()));
    }
    return true;
  case 20: {  // Direct TimePick handling to support version <=7, startTime is supposed to be correctly set
              // It is the case for file that were generated with geopsy
      static const QString tmp("index");
      XMLRestoreAttributeIterator it=attributes.find(tmp);
      if(it!=attributes.end()) {
        DateTime t=_startTime.shifted(content.toDouble(&ok));
        if(ok) {
          TimePick& p=beginModifyMetaData<TimePick>();
          p.setValue(it.value().toString(), t);
          endModifyMetaData(p);
        }
        return ok;
      } else {
        App::log(tr("Time pick name is missing\n"));
        return false;
      }
    }
  case 100: {
      MetaData * d=_optionalData.data(MetaDataFactory::instance()->id(tag.toString()));
      static const QString tmp("index");
      XMLRestoreAttributeIterator it=attributes.find(tmp);
      if(it!=attributes.end()) {
        d->fromString(it.value().toString(), content.toString());
      } else {
        d->fromString(QString(), content.toString());
      }
    }
    return true;
  case 101:
    return true;
  default:
    return false;
  }
}

bool Signal::xml_polish(XML_POLISH_ARGS)
{
  Q_UNUSED(context)
  if(_db->version()<8) {
    setMetaData(CompatibilityTimeReference()); // removal of metadata structure
  }
  return true;
}

bool Signal::readError(const char * buf)
{
  TRACE;
  App::log(tr(" # Error # reading signal ID %1 from database file\n").arg(id()) );
  delete buf;
  return false;
}

/*!
  Returns the maximum amplitude and store it in _maxAmplitude if \a itmin==0 and \a itmax==nSamples()-1
  If \a itmin==\a itmax==0, it returns directly _maxAmplitude without re-checking it.
*/
double Signal::maximumAmplitude(int itmin, int itmax) const
{
  TRACE;
  // Better if amplitude mutex is not locked while signal mutex is blocked
  // A special case was encountered with DynamicSignal::set() and a drawing thread active
  // at the same moment.
  if(itmin==0 && itmax==0) {
    QMutexLocker ml(&_amplitudeMutex);
    if(_maxAmplitude!=std::numeric_limits<double>::infinity()) return _maxAmplitude;
    itmax=_nSamples-1;
  }
  double v=DoubleSignal::maximumAmplitude(itmin, itmax);
  if(itmin <= 0 && itmax >= _nSamples-1) {
    QMutexLocker ml(&_amplitudeMutex);
    _maxAmplitude=v;
  }
  return v;
}

/*!
  Returns the time of the maximum amplitude.
*/
DateTime Signal::maximumAmplitudeAt(const TimeRange& r) const
{
  TRACE;
  double t;
  if(r.start().isNull() && r.end().isNull()) {
    t=DoubleSignal::maximumAmplitudeAt(0, nSamples());
  } else {
    double f=1.0/_samplingPeriod;
    t=DoubleSignal::maximumAmplitudeAt(qRound(_startTime.secondsTo(r.start())*f),
                                       qRound(_startTime.secondsTo(r.end())*f));
  }
  return _startTime.shifted(_samplingPeriod*t);
}

/*!
  Returns the average amplitude and store it in _averageAmplitude if \a itmin==0 and \a itmax==nSamples()-1
  If \a itmin==\a itmax==0, it returns directly _averageAmplitude without re-checking it.
*/
double Signal::averageAmplitude(int itmin, int itmax) const
{
  TRACE;
  // Better if amplitude mutex is not locked while signal mutex is blocked
  // A special case was encountered with DynamicSignal::set() and a drawing thread active
  // at the same moment.
  if(itmin==0 && itmax==0) {
    QMutexLocker ml(&_amplitudeMutex);
    if(_averageAmplitude!=std::numeric_limits<double>::infinity()) return _averageAmplitude;
    itmax=_nSamples-1;
  }
  double v=DoubleSignal::averageAmplitude(itmin, itmax);
  if(itmin <= 0 && itmax >= _nSamples-1) {
    QMutexLocker ml(&_amplitudeMutex);
    _averageAmplitude=v;
  }
  return v;
}

int Signal::compare(const Signal& o) const
{
  TRACE;
#define SIGNAL_COMPARE_KERNEL \
        if(n1<n2) comp=-1; \
        else if(n1>n2) comp=1; \
        else continue; \
        break;

#define SIGNAL_COMPARE(type, function) \
      { \
        type n1=function; \
        type n2=o.function; \
        SIGNAL_COMPARE_KERNEL \
      }

#define SIGNAL_COMPARE_STRING(function) \
      { \
        QString n1=function; \
        QString n2=o.function; \
        comp=n1.localeAwareCompare(n2); \
        if(comp==0) continue; \
        break; \
      }

  int n=SortKey::count();
  for(int i=0; i<n; i++) {
    const SortKey& k=SortKey::at(i);
    int comp;
    SAFE_UNINITIALIZED(comp,0);
    // Standard meta data
    if(k.id()<MetaDataFactory::DataCount) {
      switch (k.id()) {
      case MetaDataFactory::ID: SIGNAL_COMPARE(int, id())
      case MetaDataFactory::StartTime: SIGNAL_COMPARE(const DateTime&, startTime())
      case MetaDataFactory::SamplingPeriod: SIGNAL_COMPARE(double, samplingPeriod())
      case MetaDataFactory::NSamples: SIGNAL_COMPARE(int, nSamples())
      case MetaDataFactory::ReceiverX: SIGNAL_COMPARE(double, receiver().x())
      case MetaDataFactory::ReceiverY: SIGNAL_COMPARE(double, receiver().y())
      case MetaDataFactory::ReceiverZ: SIGNAL_COMPARE(double, receiver().z())
      case MetaDataFactory::UtmZone: SIGNAL_COMPARE_STRING(utmZone().toString())
      case MetaDataFactory::Name: SIGNAL_COMPARE_STRING(name())
      case MetaDataFactory::Type: SIGNAL_COMPARE(int, type())
      case MetaDataFactory::FileNumber: {
          SignalFilePool& filePool=_db ->filePool();
          int n1=filePool.indexOf(file());
          int n2=filePool.indexOf(o.file());
          SIGNAL_COMPARE_KERNEL
        }
      case MetaDataFactory::NumberInFile: SIGNAL_COMPARE(int , numberInFile())
      case MetaDataFactory::FileName: {
          QString n1=file() ? file()->name() : QString();
          QString n2=o.file() ? o.file()->name() : QString();
          comp=n1.localeAwareCompare(n2);
          if(comp==0) continue;
          break;
        }
      case MetaDataFactory::FileFormat: {
          QString n1=file() ? file()->format().name() : QString();
          QString n2=o.file() ? o.file()->format().name() : QString();
          comp=n1.localeAwareCompare(n2);
          if(comp==0) continue;
          break;
        }
      case MetaDataFactory::ShortFileName: {
          QString n1=file() ? file()->shortName() : QString();
          QString n2=o.file() ? o.file()->shortName() : QString();
          comp=n1.localeAwareCompare(n2);
          if(comp==0) continue;
          break;
        }
      case MetaDataFactory::Duration: SIGNAL_COMPARE(double, duration())
      case MetaDataFactory::EndTime: SIGNAL_COMPARE(DateTime, endTime())
      case MetaDataFactory::Component: SIGNAL_COMPARE(int, component())
      case MetaDataFactory::MaximumAmplitude: SIGNAL_COMPARE(double, maximumAmplitude())
      case MetaDataFactory::AverageAmplitude: SIGNAL_COMPARE(double, averageAmplitude())
      case MetaDataFactory::IsOriginalFile: SIGNAL_COMPARE(int, isOriginalFile() ? 0 : 1)
      case MetaDataFactory::SamplingFrequency: SIGNAL_COMPARE(double, samplingFrequency())
      case MetaDataFactory::Pointer:
        if(this < &o) comp=-1;
        else if(this > &o) comp=1;
        else continue;
        break;
      case MetaDataFactory::CountPerVolt: SIGNAL_COMPARE(double, countPerVolt())
      case MetaDataFactory::VoltPerCount: SIGNAL_COMPARE(double, voltPerCount())
      case MetaDataFactory::VoltPerUnit: SIGNAL_COMPARE(double, voltPerUnit())
      case MetaDataFactory::UnitPerVolt: SIGNAL_COMPARE(double, unitPerVolt())
      case MetaDataFactory::CountPerUnit: SIGNAL_COMPARE(double, countPerUnit())
      case MetaDataFactory::UnitPerCount: SIGNAL_COMPARE(double, unitPerCount())
      case MetaDataFactory::AmplitudeUnit: SIGNAL_COMPARE(int, amplitudeUnit())
      case MetaDataFactory::SampleSize: SIGNAL_COMPARE(double, dataSizeMb())
      case MetaDataFactory::HeaderModified: SIGNAL_COMPARE(bool, isHeaderModified())
      case MetaDataFactory::Dummy:      // Not comparable members
      case MetaDataFactory::DataCount:
        continue;
      }
    } else {
      // Optional meta data
      const MetaData * d1=_optionalData.data(k.id());
      const MetaData * d2;
      if(d1) {
        d2=o._optionalData.data(k.id());
        ASSERT(d2 && d1->xml_tagName()==d2->xml_tagName());
      } else {
        continue;
      }
      comp=d1->compare(k.subId(), k.index(), *d2);
      if(comp==0) continue;
    }
    if(k.reversed()) {
      return -comp;
    } else {
      return comp;
    }
  }
  return 0;
}

QVariant Signal::header(const MetaDataIndex& index) const
{
  TRACE;
  if(index.id()<MetaDataFactory::DataCount) {
    switch (index.id()) {
    case MetaDataFactory::ID: return id();
    case MetaDataFactory::StartTime: return startTime().toString(DateTime::defaultUserFormat);
    case MetaDataFactory::SamplingPeriod: return samplingPeriod();
    case MetaDataFactory::NSamples: return nSamples();
    case MetaDataFactory::Duration: return duration();
    case MetaDataFactory::EndTime: return endTime().toString(DateTime::defaultUserFormat);
    case MetaDataFactory::ReceiverX: return receiver().x();
    case MetaDataFactory::ReceiverY: return receiver().y();
    case MetaDataFactory::ReceiverZ: return receiver().z();
    case MetaDataFactory::UtmZone: return utmZone().toString();
    case MetaDataFactory::Name: return name();
    case MetaDataFactory::Component: return componentUserName();
    case MetaDataFactory::Type: return typeString(type());
    case MetaDataFactory::FileNumber: {
        return database()->filePool().indexOf(file());
      }
    case MetaDataFactory::FileName: if(file()) return file() ->name(); else return tr("### Temporary signal ###");
    case MetaDataFactory::ShortFileName: if(file()) return file() ->shortName(); else return tr("### Temporary signal ###");
    case MetaDataFactory::FileFormat: if(file()) return file()->format().name(); else return tr("### Temporary signal ###");
    case MetaDataFactory::NumberInFile: return numberInFile();
    case MetaDataFactory::MaximumAmplitude: return maximumAmplitude();
    case MetaDataFactory::AverageAmplitude: return averageAmplitude();
    case MetaDataFactory::IsOriginalFile:
      if(file() && file() ->isOriginalFile())
        return tr("Original");
      else
        return tr("Processed");
    case MetaDataFactory::SamplingFrequency: return 1.0/samplingPeriod();
    case MetaDataFactory::Pointer: return QString::number(reinterpret_cast<qint64>(this), 16);
    case MetaDataFactory::CountPerVolt: return countPerVolt();
    case MetaDataFactory::VoltPerCount: return voltPerCount();
    case MetaDataFactory::VoltPerUnit: return voltPerUnit();
    case MetaDataFactory::UnitPerVolt: return unitPerVolt();
    case MetaDataFactory::CountPerUnit: return countPerUnit();
    case MetaDataFactory::UnitPerCount: return unitPerCount();
    case MetaDataFactory::AmplitudeUnit: return amplitudeUnitUserName();
    case MetaDataFactory::SampleSize: return dataSizeMb();
    case MetaDataFactory::HeaderModified: return isHeaderModified();
    case MetaDataFactory::Dummy: return "####";
    case MetaDataFactory::DataCount: break;
    }
  } else {
    const MetaData * d=_optionalData.data(index.id());
    if(d) {
      return d->data(index.subId(), index.index());
    }
  }
  return QVariant();
}

bool Signal::setHeader(const MetaDataIndex& index, QVariant val)
{
  TRACE;
  bool ok=true;
  if(index.id()<MetaDataFactory::DataCount) {
    double v;
    SAFE_UNINITIALIZED(v, 0.0)
    switch (index.id()) {     
    case MetaDataFactory::Duration:
      v=Number::durationToSeconds(val, ok);
      break;
    case MetaDataFactory::SamplingPeriod:
    case MetaDataFactory::SamplingFrequency:
    case MetaDataFactory::ReceiverX:
    case MetaDataFactory::ReceiverY:
    case MetaDataFactory::ReceiverZ:
    case MetaDataFactory::CountPerVolt:
    case MetaDataFactory::VoltPerCount:
    case MetaDataFactory::VoltPerUnit:
    case MetaDataFactory::UnitPerVolt:
      v=Number::toDouble(val, ok);
      break;
    case MetaDataFactory::StartTime:
    case MetaDataFactory::EndTime:
    case MetaDataFactory::Name:
    case MetaDataFactory::UtmZone:
    case MetaDataFactory::Component:
    case MetaDataFactory::AmplitudeUnit:
      break;
    READONLY_STANDARD_METADATA
      return false;
    }
    if(ok) {
      switch (index.id()) {
      case MetaDataFactory::StartTime: {
          DateTime t;
          if(t.fromString(val.toString(), DateTime::defaultUserFormat)) {
            setStartTime(t);
          }
        }
        break;
      case MetaDataFactory::EndTime: {
          DateTime t;
          if(t.fromString(val.toString(), DateTime::defaultUserFormat)) {
            setStartTime(t.shifted(-duration()));
          }
        }
        break;
      case MetaDataFactory::Duration:
        setSamplingPeriod(v/static_cast<double>(_nSamples));
        break;
      case MetaDataFactory::SamplingPeriod:
        setSamplingPeriod(v);
        break;
      case MetaDataFactory::SamplingFrequency:
        setSamplingPeriod(1.0/v);
        break;
      case MetaDataFactory::ReceiverX:
        _receiver.setX(v);
        break;
      case MetaDataFactory::ReceiverY:
        _receiver.setY(v);
        break;
      case MetaDataFactory::ReceiverZ:
        _receiver.setZ(v);
        break;
      case MetaDataFactory::CountPerVolt:
        setCountPerVolt(v);
        break;
      case MetaDataFactory::VoltPerCount:
        setVoltPerCount(v);
        break;
      case MetaDataFactory::VoltPerUnit:
        setVoltPerUnit(v);
        break;
      case MetaDataFactory::UnitPerVolt:
        setUnitPerVolt(v);
        break;
      case MetaDataFactory::Name:
        _name=val.toString();
        break;
      case MetaDataFactory::UtmZone:
        _utmZone.fromString(val.toString());
        break;
      case MetaDataFactory::Component:
        setComponent(userComponent(val.toString()));
        break;
      case MetaDataFactory::AmplitudeUnit:
        setAmplitudeUnit(userAmplitudeUnit(val.toString()));
        break;
      READONLY_STANDARD_METADATA
        break;
      }
    }
  } else {
    MetaData * d=_optionalData.data(index.id());
    if(d) {
      if(!d->setData(index.subId(), index.index(), val)) {
        return false;
      }
    } else {
      return false;
    }
  }
  setHeaderModified(true);
  return true;
}

Signal * Signal::newCopy(Signal * sig)
{
  TRACE;
  Signal * newSig=new Signal(*sig);
  newSig->copySamplesFrom(sig);
  return newSig;
}

/*!
  Called only from LOCK_SAMPLES, samples are supposed to locked and available. Calls to LOCK_SAMPLES are forbidden (dead lock).
*/
bool Signal::load(double * samples) const
{
  TRACE;
  SignalFileFormat format=file()->format();
  DoubleSignal::SignalType typeInFile=DoubleSignal::Waveform;
  bool ret;
  SAFE_UNINITIALIZED(ret,0);
  switch (format.id()) {
  case SignalFileFormat::GeopsySignal:
    ret=loadGeopsySignal(samples);
    break;
  case SignalFileFormat::Seg2:
    ret=loadSeg2(samples);
    break;
  case SignalFileFormat::SegD:
    ret=loadSegD(samples);
    break;
  case SignalFileFormat::SuLittleEndian:
    ret=loadSu(samples, QDataStream::LittleEndian);
    break;
  case SignalFileFormat::SuBigEndian:
    ret=loadSu(samples, QDataStream::BigEndian);
    break;
  case SignalFileFormat::RD3:
    ret=loadRD3(samples);
    break;
  case SignalFileFormat::SacLittleEndian:
    ret=loadSac(samples, QDataStream::LittleEndian);
    break;
  case SignalFileFormat::SacBigEndian:
    ret=loadSac(samples, QDataStream::BigEndian);
    break;
  case SignalFileFormat::Radan:
    ret=loadRadan(samples);
    break;
  case SignalFileFormat::Tomo:
    ret=true;
    break;
  case SignalFileFormat::Sismalp:
    ret=loadSismalp(samples);
    break;
  case SignalFileFormat::Gse2:
  case SignalFileFormat::MultiGse2:
    ret=loadGse2(samples);
    break;
  case SignalFileFormat::SyscomXmr:
    ret=loadSyscom3Bytes(samples);
    break;
  case SignalFileFormat::SyscomSmr:
  case SignalFileFormat::SyscomVmrx:
    ret=loadSyscom2Bytes(samples);
    break;
  case SignalFileFormat::GuralpGcf:
    ret=loadGuralpGcf(samples);
    break;
  case SignalFileFormat::MiniSeed:
    ret=loadMiniSeed(samples);
    break;
  case SignalFileFormat::Wav:
    // For this format it is better in some cases to load all signals in one block
    // First check if possible, the number of samples is the same for
    // all signals in this file
    if(GeopsyCoreEngine::instance()->cache()->freeBytes()>2*dataSize()) {
      // Return here because more than one signal has been loaded
      return loadMultiChannelsWav(const_cast<Signal *>(this), samples);
    } else {
      ret=loadWav(samples); // back to conventional but slower method
    }
    break;
  case SignalFileFormat::Saf:
  case SignalFileFormat::CityShark2:
  case SignalFileFormat::MiniShark:
  case SignalFileFormat::Ascii:
    // For this format it is better in some cases to load all signals in one block
    // First check if possible, the number of samples is the same for
    // all signals in this file
    if(GeopsyCoreEngine::instance()->cache()->freeBytes()>2*dataSize()) {
      // Returns here because more than one signal has been loaded
      return loadMultiChannelsAscii(const_cast<Signal *>(this), samples);
    } else {
      ret=loadAscii(samples, tr("Loading ASCII Columns values from %1")); // back to conventional but slower method
    }
    break;
  case SignalFileFormat::AsciiOneColumn:
    ret=loadAsciiOneColumn(samples);
    break;
  case SignalFileFormat::SegYLittleEndian:
    ret=loadSegY(samples, QDataStream::LittleEndian);
    break;
  case SignalFileFormat::SegYBigEndian:
    ret=loadSegY(samples, QDataStream::BigEndian);
    break;
  case SignalFileFormat::PasscalSegYLittleEndian:
    ret=loadPasscalSegY(samples, QDataStream::LittleEndian);
    break;
  case SignalFileFormat::PasscalSegYBigEndian:
    ret=loadPasscalSegY(samples, QDataStream::BigEndian);
    break;
  case SignalFileFormat::Custom:
    ASSERT(format.customFormat());
    ret=format.customFormat()->load(this, samples);
    break;
  case SignalFileFormat::Fourier:
    // For this format it is better in some cases to load all signals in one block
    // First check if possible, the number of samples is the same for
    // all signals in this file
    //if(GeopsyCoreEngine::instance()->cache()->freeBytes()>2*dataSize()) {
      // Returns here because more than one signal has been loaded
      //return loadMultiChannelsFourier(const_cast<Signal *>(this), samples);
    //} else {
       // back to conventional but slower method
      typeInFile=DoubleSignal::Spectrum;
      ret=loadFourier(samples);
    //}
    break;
  case SignalFileFormat::Temporary:
  case SignalFileFormat::Unknown:
  case SignalFileFormat::FormatCount:
    ret=false;
    break;
  }
  if(ret) {
    DoubleSignal::SignalType st=type();
    const_cast<Signal *>(this)->setType(typeInFile);
    const_cast<Signal *>(this)->fastFourierTransform(st);
  }
  return ret;
}

bool Signal::loadMultiChannelsAscii(Signal * sig0, double * samples0)
{
  TRACE;
  int nSig;
  double ** samplePtr=nullptr;
  SubSignalPool toLoad=beginMultiChannelLoad(sig0, samples0, nSig, samplePtr);
  if(toLoad.count()>1) {
    SignalFile * file=sig0->file();
    QFileInfo fi(file->name());
    QString title=tr("Loading ASCII values from %1").arg(fi.fileName());
    QFile f(sig0->file()->name());
    if(!f.open(QIODevice::ReadOnly)) {
      App::log(title+": "+tr("Unable to open file\n"));
      delete [] samplePtr;
      endMultiChannelLoad(sig0, toLoad);
      return false;
    }
    f.seek(sig0->offsetInFile());
    QByteArray buffer=f.readAll();
    AsciiLineParser p(&buffer, nSig);
    const AsciiSignalFormat * format=static_cast<const AsciiSignalFormat *>(file->format().customFormat());
    bool hexa=false;
    if(format) {
      p.setSeparators(format->separators());
      p.setEmptyValues(format->emptyValues());
      p.setQuotes(format->quotes());
      if(format->hexadecimal()) {
        hexa=true;
      }
    } else {
      AsciiSignalFormat format;
      p.setSeparators(format.separators());
      p.setEmptyValues(format.emptyValues());
      p.setQuotes(format.quotes());
      if(format.hexadecimal()) {
        hexa=true;
      }
    }

    int ns=sig0->nSamples();
    int progressStep=ns >> 7;
    if(progressStep==0) {
      progressStep=ns;
    }
    int nextProgress=progressStep;
    GeopsyCoreEngine::instance()->setProgressMaximum(sig0->database(), ns);
    double conversionFactor=sig0->unitPerCount();
    // Parse values
    bool missingColumnReported=false;
    int is=0;
    if(hexa) {
      while(is<ns) {
        while(is<nextProgress) {
          p.readLine();
          int n=p.count();
          if(n!=nSig) {
            if(n==0) {
              App::log(title+": "+tr("Reaching the end of file at sample %1, missing samples\n")
                                         .arg(is));
              is=ns;
              break;
            }
            if(!missingColumnReported){
              App::log(title+": "+tr("Missing %1 column(s) for sample %2\n")
                                        .arg(nSig-n).arg(is));
              missingColumnReported=true;
            }
          }
          for(int iSig=0; iSig<n; iSig++) {
            double *& v=samplePtr[iSig];
            if(v) {
              *v=p.hexaToInt(iSig)*conversionFactor;
              v++;
            }
          }
          is++;
        }
        GeopsyCoreEngine::instance()->setProgressValue(sig0->database(), is);
        nextProgress+=progressStep;
        if(nextProgress>ns) {
          nextProgress=ns;
        }
      }
    } else {
      while(is<ns) {
        while(is<nextProgress) {
          p.readLine();
          int n=p.count();
          if(n!=nSig) {
            if(n==0) {
              App::log(title+": "+tr("Reaching the end of file at sample %1, missing samples\n")
                                         .arg(is));
              is=ns;
              break;
            }
            if(!missingColumnReported){
              App::log(title+": "+tr("Missing %1 column(s) for sample %2\n")
                                                    .arg(nSig-n).arg(is));
              missingColumnReported=true;
            }
          }
          for(int iSig=0; iSig<n; iSig++) {
            double *& v=samplePtr[iSig];
            if(v) {
              *v=p.toDouble(iSig)*conversionFactor;
              v++;
            }
          }
          is++;
        }
        GeopsyCoreEngine::instance()->setProgressValue(sig0->database(), is);
        nextProgress+=progressStep;
        if(nextProgress>ns) {
          nextProgress=ns;
        }
      }
    }
    if(nextProgress<ns) {  // Make sure that progress finishes.
      GeopsyCoreEngine::instance()->setProgressValue(sig0->database(), ns);
    }
    endMultiChannelLoad(sig0, toLoad);
  } else {
    sig0->loadAscii(samples0, tr("Loading ASCII Columns values from %1"));
    DoubleSignal::SignalType st=sig0->type();
    sig0->setType(DoubleSignal::Waveform);
    sig0->fastFourierTransform(st);
  }
  delete [] samplePtr;
  return toLoad.count()>=1;
}

bool Signal::loadMultiChannelsFourier(Signal * sig0, double * samples0)
{
  TRACE;
  int nSig;
  double ** samplePtr=nullptr;
  SubSignalPool toLoad=beginMultiChannelLoad(sig0, samples0, nSig, samplePtr);
  if(toLoad.count()>1) {
    QFileInfo fi(sig0->file()->name());
    QString title=tr("Loading Fourier spectrum from %1").arg(fi.fileName());
    QFile f(sig0->file()->name());
    if(!f.open(QIODevice::ReadOnly)) {
      App::log(title+": "+tr("Unable to open file\n"));
      delete [] samplePtr;
      endMultiChannelLoad(sig0, toLoad);
      return false;
    }
    QByteArray buffer=f.readAll();
    AsciiLineParser p(&buffer, nSig);

    int ns=sig0->nSamples();
    int progressStep=ns >> 7;
    if(progressStep==0) {
      progressStep=ns;
    }
    int nextProgress=progressStep;
    GeopsyCoreEngine::instance()->setProgressMaximum(sig0->database(), ns);
    double conversionFactor=sig0->unitPerCount();
    // Parse values
    bool missingColumnReported=false;
    int is=0;
    while(is<ns) {
      while(is<nextProgress) {
        p.readLine();
        int n=p.count();
        if(n!=nSig) {
          if(n==0) {
            App::log(title+": "+tr("Reaching the end of file at sample %1, missing samples\n")
                                       .arg(is));
            is=ns;
            break;
          }
          if(!missingColumnReported){
            App::log(title+": "+tr("Missing %1 column(s) for sample %2\n")
                                                  .arg(nSig-n).arg(is));
            missingColumnReported=true;
          }
        }
        for(int iSig=0; iSig<n; iSig++) {
          double *& v=samplePtr[iSig];
          *v=p.toDouble(iSig)*conversionFactor;
          v++;
        }
        is++;
      }
      GeopsyCoreEngine::instance()->setProgressValue(sig0->database(), is);
      nextProgress+=progressStep;
      if(nextProgress>ns) {
        nextProgress=ns;
      }
    }
    if(nextProgress<ns) {  // Make sure that progress finishes.
      GeopsyCoreEngine::instance()->setProgressValue(sig0->database(), ns);
    }
    endMultiChannelLoad(sig0, toLoad, DoubleSignal::Spectrum);
  } else {
    sig0->loadFourier(samples0);
    DoubleSignal::SignalType st=sig0->type();
    sig0->setType(DoubleSignal::Spectrum);
    sig0->fastFourierTransform(st);
  }
  delete [] samplePtr;
  return toLoad.count()>=1;
}

bool Signal::loadMultiChannelsWav (Signal * sig0, double * samples0)
{
  TRACE;
  int nSig;
  double ** samplePtr=nullptr;
  SubSignalPool toLoad=beginMultiChannelLoad(sig0, samples0, nSig, samplePtr);
  if(toLoad.count()>1) {
    // Open file
    QFileInfo fi(sig0->file() ->name());
    QString title="Loading WAVE PCM soundfile values " + fi.fileName();
    FILE *fpin;
    if((fpin=fopen(sig0->file() ->name().toLatin1().data(), "rb"))==nullptr) {
      Message::warning(MSG_ID, title, "Unable to open file", Message::cancel());
      delete [] samplePtr;
      endMultiChannelLoad(sig0, toLoad);
      return false;
    }
    int ns=sig0->nSamples();
    int bufLen=sig0->offsetInFile() * ns;
    char * buf=new char [bufLen];
    char * bufPtr=buf;
    fseek(fpin, 44, SEEK_SET);
    if(static_cast<int>(fread(buf, sizeof(char), bufLen, fpin))!=bufLen) {
      Message::warning(MSG_ID, title, "Error reading file", Message::cancel());
      delete [] samplePtr;
      endMultiChannelLoad(sig0, toLoad);
      return false;
    }

    int oldTs=GeopsyCoreEngine::instance()->progressMaximum(sig0->database());
    GeopsyCoreEngine::instance()->setProgressMaximum(sig0->database(), ns);
    double convFact=sig0->unitPerCount();
    // Read values in file
    for(int is=0;is < ns;is++) {
      for(int iSig=0; iSig<nSig; iSig++) {
        double *& v=samplePtr[ iSig ];
        if(v) {
          *v=(*reinterpret_cast<qint16*>(bufPtr))*convFact;
          v++;
          bufPtr+=sizeof(qint16);
        }
      }
      GeopsyCoreEngine::instance()->setProgressValue(sig0->database(), is);
    }
    GeopsyCoreEngine::instance()->setProgressMaximum(sig0->database(), oldTs);
    fclose(fpin);
    delete [] buf;
    endMultiChannelLoad(sig0, toLoad);
  } else if(toLoad.count()==1) {
    sig0->loadWav(samples0);
    DoubleSignal::SignalType st=sig0->type();
    sig0->setType(DoubleSignal::Waveform);
    sig0->fastFourierTransform(st);
  }
  delete [] samplePtr;
  return toLoad.count()>=1;
}


SubSignalPool Signal::beginMultiChannelLoad(Signal * sig0, double * samples0,
                                            int& nSig, double **& samplePtr)
{
  TRACE;
  SubSignalPool sigsToLoad;
  // Get the list of signal in this file
  SubSignalPool subpool;
  subpool.addFile(sig0->file());
  // Get the maximum number of columns to load, if some columns are ignored subpool.count()
  // cannot be used directly. We have to get the highest numberInFile()
  nSig=0;
  for(SubSignalPool::iterator it=subpool.begin(); it!=subpool.end(); it++) {
    Signal * sig=*it;
    if(nSig<sig->numberInFile()) {
      nSig=sig->numberInFile();
    }
  }
  nSig++;
  // Get a list of ptr to samples of signals to load
  samplePtr=new double * [nSig];
  for(int i=0; i<nSig; i++) {
    samplePtr[i]=nullptr;
  }
  // Lock samples for all unloaded signals
  for(SubSignalPool::iterator it=subpool.begin(); it!=subpool.end(); it++) {
    Signal * sig=*it;
    if(sig==sig0) {
      samplePtr[sig->numberInFile()]=samples0;
      sigsToLoad.addSignal(sig);
    } else {
      sig->lockData();
      if(!sig->isAllocated() && !sig->isSaved()) {
        samplePtr[sig->numberInFile()]=sig->DoubleSignal::lockSamples();
        if(samplePtr[sig->numberInFile()]) {
          sigsToLoad.addSignal(sig);
        }
      }
      sig->unlockData();
    }
  }
  return sigsToLoad;
}

void Signal::endMultiChannelLoad(Signal * sig0, const SubSignalPool& loadedSigs, DoubleSignal::SignalType typeInFile)
{
  for(SubSignalPool::const_iterator it=loadedSigs.begin(); it!=loadedSigs.end(); it++) {
    Signal * sig=*it;
    DoubleSignal::SignalType st=sig->type();
    sig->setType(typeInFile);
    sig->fastFourierTransform(st);
    if(sig!=sig0) sig->unlockSamples();
  }
}

/*!
  Set current recorder factor for converting counts into volts
*/
void Signal::setCountPerVolt(double c)
{
  TRACE;
  if(c!=_countPerVolt && c>0.0) {
    lockData(); // Lock without allocating the samples
    if(isAllocated()) { // It is no longer necessary to fix swaped signal
      LOCK_SAMPLES(double, thisSamples, this)
        multiply(_countPerVolt/c);
        resetAmplitudes();
      UNLOCK_SAMPLES(this)
    }
    unlockData();
    _countPerVolt=c;
  }
}

/*!
  Set current sensor factor for converting volts into amplitudeUnit
*/
void Signal::setVoltPerUnit(double c)
{
  TRACE;
  if(c!=_voltPerUnit && c>0.0) {
    lockData(); // Lock without allocating the samples
    if(isAllocated()) { // It is no longer necessary to fix swaped signal
      LOCK_SAMPLES(double, thisSamples, this)
        multiply(_voltPerUnit/c);
        resetAmplitudes();
      UNLOCK_SAMPLES(this)
    }
    unlockData();
    _voltPerUnit=c;
  }
}

QString Signal::componentLetter(Components c)
{
  TRACE;
  switch (c) {
  case Vertical:
    return tr("Z");
  case East:
    return tr("E");
  case North:
    return tr("N");
  case All:
    return tr("A");
  case Horizontal:
    return tr("H");
  case Ignore:      // For Custom ASCII formats, let user ignore some columns
    return tr("I");
  case Time:        // For Custom ASCII formats, if there is time stamp column
    return tr("T");
  case UndefinedComponent:
    break;
  }
  return tr("?");
}

/*!
  Return name for component in user's own language
*/
QString Signal::userName(Components c)
{
  TRACE;
  switch (c) {
  case Vertical:
    return tr("Vertical");
  case North:
    return tr("North");
  case East:
    return tr("East");
  case All:
    return tr("All");
  case Horizontal:
    return tr("Horizontal");
  case Ignore:
    return tr("Ignore");
  case Time:
    return tr("Time");
  case UndefinedComponent:
    break;
  }
  return tr("Undefined");
}

/*!
  Return standard name for component
*/
QString Signal::standardName(Components c)
{
  TRACE;
  switch (c) {
  case Vertical:
    return "Vertical";
  case North:
    return "North";
  case East:
    return "East";
  case All:
    return "All";
  case Horizontal:
    return "Horizontal";
  case Ignore:
    return "Ignore";
  case Time:
    return "Time";
  case UndefinedComponent:
    break;
  }
  return "Undefined";
}

/*!
  Returns component from a string entered by the user (hence in its own language)
*/
Signal::Components Signal::userComponent(QString c)
{
  TRACE;
  static QMap<QString, Components> m;
  if(m.isEmpty()) {
    m.insert(tr("Vertical"), Vertical);
    m.insert(tr("North"), North);
    m.insert(tr("East"), East);
    m.insert(tr("Horizontal"), Horizontal);
    m.insert(tr("Ignore"), Ignore);
    m.insert(tr("Time"), Time);
    m.insert(tr("All"), All);
  }
  QMap<QString,Components>::iterator it=m.find(c);
  if(it!=m.end()) {
    return it.value();
  } else {
    return UndefinedComponent;
  }
}

/*!
  Returns component from a string containing standardized component names
*/
Signal::Components Signal::standardComponent(QString c)
{
  TRACE;
  static QMap<QString, Components> m;
  if(m.isEmpty()) {
    m.insert("Vertical", Vertical);
    m.insert("North", North);
    m.insert("East", East);
    m.insert("Horizontal", Horizontal);
    m.insert("Ignore", Ignore);
    m.insert("Time", Time);
    m.insert("All", All);
  }
  QMap<QString,Components>::iterator it=m.find(c);
  if(it!=m.end()) {
    return it.value();
  } else {
    return UndefinedComponent;
  }
}

/*!
  Returns the theoretical name of the units for amplitude.
*/
QString Signal::standardName(AmplitudeUnits u)
{
  TRACE;
  switch (u) {
  case Displacement:
    return "Displacement";
  case Velocity:
    return "Velocity";
  case Acceleration:
    return "Acceleration";
  default:
    break;
  }
  return "Custom";
}

/*!
  Returns the theoretical name of the units for amplitude in user own language
*/
QString Signal::userName(AmplitudeUnits u)
{
  TRACE;
  switch (u) {
  case Displacement:
    return tr("Displacement");
  case Velocity:
    return tr("Velocity");
  case Acceleration:
    return tr("Acceleration");
  default:
    break;
  }
  return tr("Custom");
}

/*!
  Return amplitude unit from a string entered by the user (hence in its own language)
*/
Signal::AmplitudeUnits Signal::userAmplitudeUnit(QString u)
{
  TRACE;
  static QMap<QString, AmplitudeUnits> unitMap;
  if(unitMap.isEmpty()) {
    unitMap.insert(tr("Displacement"), Displacement);
    unitMap.insert(tr("Velocity"), Velocity);
    unitMap.insert(tr("Acceleration"), Acceleration);
  }
  QMap<QString,AmplitudeUnits>::iterator it=unitMap.find(u);
  if(it!=unitMap.end()) {
    return it.value();
  } else {
    return UndefinedUnit;
  }
}

/*!
  Returns amplitude unit from a string containing standardized names
*/
Signal::AmplitudeUnits Signal::standardAmplitudeUnit(QString u)
{
  TRACE;
  static QMap<QString, AmplitudeUnits> unitMap;
  if(unitMap.isEmpty()) {
    unitMap.insert("Displacement", Displacement);
    unitMap.insert("Velocity", Velocity);
    unitMap.insert("Acceleration", Acceleration);
  }
  QMap<QString,AmplitudeUnits>::iterator it=unitMap.find(u);
  if(it!=unitMap.end()) {
    return it.value();
  } else {
    return UndefinedUnit;
  }
}

/*!
  Returns the name of the units for amplitude depends upon the availability of countPerVolt() and
  voltPerUnit() (these values must be different than 1).
*/
QString Signal::effectiveAmplitudeUnit() const
{
  TRACE;
  if(_voltPerUnit!=1.0) {
    switch (_amplitudeUnit) {
    case UndefinedUnit:
      return "n/a";
    case Displacement:
      return "m";
    case Velocity:
      return "m/s";
    case Acceleration:
      return "m/s^2";
    }
  } else if(_countPerVolt!=1.0) {
    return "V";
  }
  return "counts";
}

/*!
  \fn void Signal::setTimePick(int index, double time)
  Set \a time for pick with \a index.
  Time picks are optional data members
*/

/*!
  Correlates signal \a s1 to \a s2 and saves results in this signal.
  If this signal has the correct number of samples and the correct sampling frequency, the value of this signal
  are not cleaned and the results are stacked on existing values. If not, the signal is initialized with 0.
*/
void Signal::correlation(const Signal * s1, const Signal * s2, double maxDelay)
{
  TRACE;
  if(!DoubleSignal::correlation(s1, s2, maxDelay+fabs(s2->startTime().secondsTo(s1->startTime())))) {
    App::log(tr("Error calculating correlation between signals %1 and %2, probably an allocation error\n")
                       .arg(s1->nameComponent()).arg(s2->nameComponent()));
  }
  // Change startTime according to tw and set x as the interdistance, name is the compound of both name
  // Use the effective maxDelay (given by signal lenght)
  maxDelay=(nSamples()-1)*samplingPeriod()*0.5;
  // Adjust for differences in startTime
  setStartTime(DateTime(QDate(2000, 1, 1), 0, 0.0).shifted(-maxDelay+s2->startTime().secondsTo(s1->startTime())));
  setReceiver(Point(s1->receiver().distanceTo(s2->receiver()), 0.0, 0.0));
  if(s1->component()==s2->component()) {
    setName(s1->name() + " & " + s2->name());
    setComponent(s1->component());
  } else {
    setName(s1->nameComponent() + " & " + s2->nameComponent());
    setComponent(UndefinedComponent);
  }
  setType(Waveform);
}

/*!
  Correlates signal \a s1 to \a s2 and saves results in this signal.
*/
void Signal::normalizedCorrelation(const Signal * s1, const Signal * s2, double maxDelay)
{
  TRACE;
  if(!DoubleSignal::normalizedCorrelation(s1, s2, maxDelay+fabs(s2->startTime().secondsTo(s1->startTime())))) {
    App::log(tr("Error calculating normalized correlation between signals %1 and %2, probably an allocation error\n")
                       .arg(s1->nameComponent()).arg(s2->nameComponent()));
  }
  // Change startTime according to tw and set x as the interdistance, name is the compound of both name
  // Use the effective maxDelay (given by signal lenght)
  maxDelay=(nSamples()-1)*samplingPeriod()*0.5;
  // Adjust for differences in startTime
  setStartTime(DateTime(QDate(2000, 1, 1), 0, 0.0).shifted(-maxDelay+s2->startTime().secondsTo(s1->startTime())));
  setReceiver(Point(s1->receiver().distanceTo(s2->receiver()), 0.0, 0.0));
  if(s1->component()==s2->component()) {
    setName(s1->name() + " & " + s2->name());
    setComponent(s1->component());
  } else {
    setName(s1->nameComponent() + " & " + s2->nameComponent());
    setComponent(UndefinedComponent);
  }
  setType(Waveform);
}

/*!
  Creates a new signal on range \a r. If the range of this signal and \a r do not match,
  null is returned. If \a r limits do not match with signal sampling, range is rounded to first
  and last sample included in range \a r.
*/
Signal * Signal::cut(TimeRange r) const
{
  TRACE;
  // Find the intersection with this time range
  // rthis has always a limits compatible with sampling
  TimeRange rthis=timeRange().range();
  r=r.intersection(rthis);
  // Adjust start of r to match sig sampling
  double roundedStart=startTime().secondsTo(r.start())/samplingPeriod();
  if(roundedStart<0.0) {
    r.setStart(startTime());
  } else {
    double sampleFrac=roundedStart-floor(roundedStart);
    if(sampleFrac>1e-7) { // 1e-7 sampling period precision
      r.setStart(startTime().shifted(ceil(roundedStart)*samplingPeriod()));
      int n=r.lengthSamples(samplingFrequency());
      if(r.start()+n*samplingPeriod()>r.end()) { // Check if rounding to sampling period passes over the end of range
                                                 // If so, remove one sample.
        r.setEnd(r.start()+(n-1)*samplingPeriod());
      }
    }
  }
  // If not null, extract the cut signal
  if(r.lengthSeconds()>0.0) {
    Signal * newSig=new Signal(*this);
    newSig->setStartTime(r.start());
    newSig->setNSamples(r.lengthSamples(samplingFrequency()));
    newSig->setTimeRange(timeRange());
    newSig->copySamplesFrom(this, r);
    return newSig;
  } else {
    return nullptr;
  }
}

QString Signal::nameComponent() const
{
  if(_name.isEmpty()) {
    if(component()==Signal::UndefinedComponent) {
      return "id " + QString::number(_id);
    } else {
      return "id " + QString::number(_id) + componentLetter(_component);
    }
  } else {
    if(component()==Signal::UndefinedComponent) {
      return _name;
    } else {
      return _name + " " + componentLetter(_component);
    }
  }
}

/*!
  startTime is stored at two places: in _startTime and in _timeRange.
  _startTime is used in many places and access to the start time in
  _timeRange is not direct, so to save time, we kept this redundancy.

  Do not modify _startTime directly.
*/
void Signal::setStartTime(const DateTime& t)
{
  TRACE;
  if(_timeRange.range().start().isValid() && _startTime.isValid()) {
    _timeRange.shift(_startTime.secondsTo(t));
    _startTime=t;
  } else {
    _timeRange.clear();
    _startTime=t;
    _timeRange.add(TimeRange(_startTime, endTime()));
  }
}

void Signal::setSamplingPeriod(double newVal)
{
  TRACE;
  if(newVal<=0.0 || !std::isnormal(newVal)) {
    App::log(tr("signal id %1: not a valid sampling period (%2)\n").arg(id()).arg(newVal) );
  } else {
    if(samplingPeriod()>0.0) {
      _timeRange.scale(startTime(), newVal/samplingPeriod());
      DoubleSignal::setSamplingPeriod(newVal);
    } else {
      DoubleSignal::setSamplingPeriod(newVal);
      _timeRange.clear();
      _timeRange.add(TimeRange(startTime(), endTime()));
    }
  }
}

void Signal::setSamplingFrequency(double newVal)
{
  TRACE;
  if(newVal<=0.0) {
    App::log(tr("Negative or null sampling frequency (%1) not accepted\n").arg(newVal) );
  } else {
    setSamplingPeriod(1.0/newVal);
  }
}

void Signal::setNSamples(int n)
{
  TRACE;
  DoubleSignal::setNSamples(n);
  _timeRange.clear();
  _timeRange.add(TimeRange(startTime(), endTime()));
}

/*!
  Set available signal within the range defined by t0() and endTime().
*/
void Signal::setTimeRange(const SparseTimeRange& r)
{
  TimeRange r0(_startTime, endTime());
  if(r.isNull() || _samplingPeriod==0.0) {
    _timeRange=r0;
  } else {
    _timeRange=r.intersection(r0);
    // timeRange must be kept compatible with t0 and samplingFrequency
    _timeRange.round(_startTime, _samplingPeriod);
  }
}

/*!
  Returns the time of the nearest sample for time \a t.
*/
DateTime Signal::roundTime(const DateTime& t) const
{
  double dt=qRound(_startTime.secondsTo(t)/_samplingPeriod)*_samplingPeriod;
  return _startTime.shifted(dt);
}

/*!
  \a c is a tree-letter code from Global Seismographic Network naming convention
  for recording channels. This code is based on Seed manual appendix A
  (http://www.iris.edu/manuals/SEED_appA.htm).

  If a geopsy component can be deduced, returned value is something else
  than UndefinedComponent.
*/
Signal::Components Signal::globalSeismographicNetworkComponent(const char * c)
{
  TRACE;
  if(strlen(c)!=3) {
    return UndefinedComponent;
  }
  // First letter, Band Code
  switch(c[0]) {
  case 'F': //                               sample rate=[1000, 5000[ Hz, corner period >= 10 sec
  case 'G': //                               sample rate=[1000, 5000[ Hz, corner period < 10 sec
  case 'D': //                               sample rate=[250, 1000[ Hz, corner period >= 10 sec
  case 'C': //                               sample rate=[250, 1000[ Hz, corner period < 10 sec
  case 'E': // Extremely Short Period        sample rate=[80, 250[ Hz, corner period < 10 sec
  case 'S': // Short Period                  sample rate=[10, 80[ Hz, corner period < 10 sec
  case 'H': // High Broad Band               sample rate=[80, 250[ Hz, corner period >= 10 sec
  case 'B': // Broad Band                    sample rate=[10, 80[ Hz, corner period >= 10 sec
  case 'M': // Mid Period                    sample rate=]1, 10[ Hz
  case 'L': // Long Period                   sample rate=1 Hz
  case 'V': // Very Long Period              sample rate=0.1 Hz
  case 'U': // Ultra Long Period             sample rate=0.01 Hz
  case 'R': // Extremely Long Period         sample rate=0.001 Hz
  case 'P': // On the order of 0.1 to 1 day  sample rate=[0.00001, 0.0001[
  case 'T': // On the order of 1 to 10 days  sample rate=[0.000001, 0.00001[
  case 'Q': // Greater than 10 days          sample rate<0.000001
  case 'A': // Administrative Instrument Channel
  case 'O': // Opaque Instrument Channel
    break;
  default:
    return UndefinedComponent;
  }
  // Second and third letters, Instrument Code and Orientation Code
  // Only seismometers and geophones are recognized
  switch(c[1]) {
  case 'H': // High Gain Seismometer
  case 'L': // Low Gain Seismometer
  case 'G': // Gravimeter
  case 'M': // Mass Position Seismometer
  case 'N': // Accelerometer
  case 'P': // Geophone
    switch(c[2]) {
    case 'Z':
      return Vertical;
    case 'N':
      return North;
    case 'E':
      return East;
    default:
      break;
    }
    break;
  default:
    break;
  }
  return UndefinedComponent;
}

  /*!
    \fn template <class MetaDataClass> void shareMetaData(Signal * sig)

    Shares MetaDataClass with another signal \a sig.
  */

  /*!
    \fn template <class MetaDataClass> inline const MetaDataClass& metaData() const

    This is the classical function to access values of metadata MetaDataClass.
  */

  /*!
    \fn template <class MetaDataClass> void setMetaData(const MetaDataClass& d)

    Assigns metadata \a d. If \a d is equal to the default value, the internal metadata
    is removed.

    \sa beginModifyMetaData()
  */

  /*!
    \fn template <class MetaDataClass> MetaDataClass& beginModifyMetaData()

    Used with endModifyMetaData(), an optional metadata can be modified without
    a complete assignement like in setMetaData(). This is usefull for complex
    metadata. If \a d is equal to the default value, the internal metadata
    is removed.

    \sa endModifyMetaData(), setMetaData()
  */
  /*!
    \fn template <class MetaDataClass> endModifyMetaData(const MetaDataClass& d)

    To be called right after the modification a metadata started with beginModifyMetaData().

    \sa beginModifyMetaData(), setMetaData()
  */

  /*!
    \fn const MetaDataMap& metaDataMap() const

    Internally used by SharedMetaData
  */

TIME Signal::t0AsTIME() const
{
  return _startTime.pitsaTime();
}

TIME Signal::endTimeAsTIME() const
{
  return endTime().pitsaTime();
}

/*!
  Shift signal with a fine dt even lower than sampling period.
  Use setStartTime() to shift of a few samples.
*/
bool Signal::shift(double dt)
{
  TRACE;
  _timeRange.shift(dt);
  _startTime.addSeconds(dt);
  return DoubleSignal::shift(dt);
}

/*!
  If any get the single SeismicEvent corresponding to this signal
*/
Point Signal::source() const
{
  QList<const SeismicEvent *> events=database()->seismicEvents()->events(this);
  if(events.count()==1) {
    return events.first()->position();
  } else {
    App::log(tr("Several or no seismic events for signal '%1', setting a null source.\n").arg(nameComponent()));
    return Point();
  }
}


} // namespace GeopsyCore
