You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2090 lines
69 KiB
C

/***************************************************************************
* Generic routines to pack miniSEED records using an MS3Record as a
* header template and data source.
*
* This file is part of the miniSEED Library.
*
* Copyright (c) 2023 Chad Trabant, EarthScope Data Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "libmseed.h"
#include "mseedformat.h"
#include "packdata.h"
#include "extraheaders.h"
/* Internal from another source file */
extern double ms_nomsamprate (int factor, int multiplier);
/* Function(s) internal to this file */
static int msr3_pack_mseed3 (const MS3Record *msr, void (*record_handler) (char *, int, void *),
void *handlerdata, int64_t *packedsamples,
uint32_t flags, int8_t verbose);
static int msr3_pack_mseed2 (const MS3Record *msr, void (*record_handler) (char *, int, void *),
void *handlerdata, int64_t *packedsamples,
uint32_t flags, int8_t verbose);
static int msr_pack_data (void *dest, void *src, int maxsamples, int maxdatabytes,
char sampletype, int8_t encoding, int8_t swapflag,
uint16_t *byteswritten, const char *sid, int8_t verbose);
static int ms_genfactmult (double samprate, int16_t *factor, int16_t *multiplier);
static uint32_t ms_timestr2btime (const char *timestr, uint8_t *btime, const char *sid, int8_t swapflag);
/**********************************************************************/ /**
* @brief Pack data into miniSEED records.
*
* Packing is performed according to the version at
* @ref MS3Record.formatversion.
*
* The @ref MS3Record.datasamples array and @ref MS3Record.numsamples
* value will __not__ be changed by this routine. It is the
* responsibility of the calling routine to adjust the data buffer if
* desired.
*
* As each record is filled and finished they are passed to \a
* record_handler() which should expect 1) a \c char* to the record,
* 2) the length of the record and 3) a pointer supplied by the
* original caller containing optional private data (\a handlerdata).
* It is the responsibility of \a record_handler() to process the
* record, the memory will be re-used or freed when \a
* record_handler() returns.
*
* The following data encodings and expected @ref MS3Record.sampletype
* are supported:
* - ::DE_TEXT (0), Text, expects type \c 't'
* - ::DE_INT16 (1), 16-bit integer, expects type \c 'i'
* - ::DE_INT32 (3), 32-bit integer, expects type \c 'i'
* - ::DE_FLOAT32 (4), 32-bit float (IEEE), expects type \c 'f'
* - ::DE_FLOAT64 (5), 64-bit float (IEEE), expects type \c 'd'
* - ::DE_STEIM1 (10), Stiem-1 compressed integers, expects type \c 'i'
* - ::DE_STEIM2 (11), Stiem-2 compressed integers, expects type \c 'i'
*
* If \a flags has ::MSF_FLUSHDATA set, all of the data will be packed
* into data records even though the last one will probably be smaller
* than requested or, in the case of miniSEED 2, unfilled.
*
* Default values are: record length = 4096, encoding = 11 (Steim2).
* The defaults are triggered when \a msr.reclen and \a msr.encoding
* are set to -1.
*
* @param[in] msr ::MS3Record containing data to pack
* @param[in] record_handler() Callback function called for each record
* @param[in] handlerdata A pointer that will be provided to the \a record_handler()
* @param[out] packedsamples The number of samples packed, returned to caller
* @param[in] flags Bit flags used to control the packing process:
* @parblock
* - \c ::MSF_FLUSHDATA : Pack all data in the buffer
* - \c ::MSF_PACKVER2 : Pack miniSEED version 2 regardless of ::MS3Record.formatversion
* @endparblock
* @param[in] verbose Controls logging verbosity, 0 is no diagnostic output
*
* @returns the number of records created on success and -1 on error.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
int
msr3_pack (const MS3Record *msr, void (*record_handler) (char *, int, void *),
void *handlerdata, int64_t *packedsamples, uint32_t flags, int8_t verbose)
{
int packedrecs = 0;
if (!msr)
{
ms_log (2, "Required argument not defined: 'msr'\n");
return -1;
}
if (!record_handler)
{
ms_log (2, "callback record_handler() function pointer not set!\n");
return -1;
}
if ((msr->reclen != -1) && (msr->reclen < MINRECLEN || msr->reclen > MAXRECLEN))
{
ms_log (2, "%s: Record length is out of range: %d\n", msr->sid, msr->reclen);
return -1;
}
/* Pack version 2 if requested */
if (msr->formatversion == 2 || flags & MSF_PACKVER2)
{
packedrecs = msr3_pack_mseed2 (msr, record_handler, handlerdata, packedsamples,
flags, verbose);
}
/* Pack version 3 otherwise */
else
{
packedrecs = msr3_pack_mseed3 (msr, record_handler, handlerdata, packedsamples,
flags, verbose);
}
return packedrecs;
} /* End of msr3_pack() */
/***************************************************************************
* msr3_pack_mseed3:
*
* Pack data into miniSEED version 3 record(s).
*
* Returns the number of records created on success and -1 on error.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
int
msr3_pack_mseed3 (const MS3Record *msr, void (*record_handler) (char *, int, void *),
void *handlerdata, int64_t *packedsamples,
uint32_t flags, int8_t verbose)
{
char *rawrec = NULL;
char *encoded = NULL; /* Separate encoded data buffer for alignment */
int8_t swapflag;
int dataoffset = 0;
int samplesize;
int maxdatabytes;
int maxsamples;
int recordcnt = 0;
int packsamples;
int packoffset;
int64_t totalpackedsamples;
int32_t reclen;
int32_t maxreclen;
int8_t encoding;
uint32_t crc;
uint16_t datalength;
nstime_t nextstarttime;
uint16_t year;
uint16_t day;
uint8_t hour;
uint8_t min;
uint8_t sec;
uint32_t nsec;
if (!msr)
{
ms_log (2, "Required argument not defined: 'msr'\n");
return -1;
}
if (!record_handler)
{
ms_log (2, "callback record_handler() function pointer not set!\n");
return -1;
}
/* Use default record length and encoding if needed */
maxreclen = (msr->reclen == -1) ? MS_PACK_DEFAULT_RECLEN : msr->reclen;
encoding = (msr->encoding == -1) ? MS_PACK_DEFAULT_ENCODING : msr->encoding;
if (maxreclen < (MS3FSDH_LENGTH + strlen(msr->sid) + msr->extralength))
{
ms_log (2, "%s: Record length (%d) is not large enough for header (%d), SID (%"PRIsize_t"), and extra (%d)\n",
msr->sid, maxreclen, MS3FSDH_LENGTH, strlen(msr->sid), msr->extralength);
return -1;
}
/* Check to see if byte swapping is needed, miniSEED 3 is little endian */
swapflag = (ms_bigendianhost ()) ? 1 : 0;
/* Allocate space for data record */
rawrec = (char *)libmseed_memory.malloc (maxreclen);
if (rawrec == NULL)
{
ms_log (2, "%s: Cannot allocate memory\n", msr->sid);
return -1;
}
/* Pack fixed header and extra headers, returned size is data offset */
dataoffset = msr3_pack_header3 (msr, rawrec, maxreclen, verbose);
if (dataoffset < 0)
{
ms_log (2, "%s: Cannot pack miniSEED version 3 header\n", msr->sid);
return -1;
}
/* Short cut: if there are no samples, record packing is complete */
if (msr->numsamples <= 0)
{
/* Set encoding to text for consistency and to reduce expectations */
*pMS3FSDH_ENCODING(rawrec) = DE_TEXT;
/* Calculate CRC (with CRC field set to 0) and set */
memset (pMS3FSDH_CRC(rawrec), 0, sizeof(uint32_t));
crc = ms_crc32c ((const uint8_t*)rawrec, dataoffset, 0);
*pMS3FSDH_CRC(rawrec) = HO4u (crc, swapflag);
if (verbose >= 1)
ms_log (0, "%s: Packed %d byte record with no payload\n", msr->sid, dataoffset);
/* Send record to handler */
record_handler (rawrec, dataoffset, handlerdata);
libmseed_memory.free (rawrec);
if (packedsamples)
*packedsamples = 0;
return 1;
}
samplesize = ms_samplesize (msr->sampletype);
if (!samplesize)
{
ms_log (2, "%s: Unknown sample type '%c'\n", msr->sid, msr->sampletype);
return -1;
}
/* Determine the max data bytes and sample count */
maxdatabytes = maxreclen - dataoffset;
if (encoding == DE_STEIM1)
{
maxsamples = (int)(maxdatabytes / 64) * STEIM1_FRAME_MAX_SAMPLES;
}
else if (encoding == DE_STEIM2)
{
maxsamples = (int)(maxdatabytes / 64) * STEIM2_FRAME_MAX_SAMPLES;
}
else
{
maxsamples = maxdatabytes / samplesize;
}
/* Allocate space for encoded data separately for alignment */
if (msr->numsamples > 0)
{
encoded = (char *)libmseed_memory.malloc (maxdatabytes);
if (encoded == NULL)
{
ms_log (2, "%s: Cannot allocate memory\n", msr->sid);
libmseed_memory.free (rawrec);
return -1;
}
}
/* Pack samples into records */
totalpackedsamples = 0;
packoffset = 0;
if (packedsamples)
*packedsamples = 0;
while ((msr->numsamples - totalpackedsamples) > maxsamples || flags & MSF_FLUSHDATA)
{
packsamples = msr_pack_data (encoded,
(char *)msr->datasamples + packoffset,
(int)(msr->numsamples - totalpackedsamples), maxdatabytes,
msr->sampletype, encoding, swapflag,
&datalength, msr->sid, verbose);
if (packsamples < 0)
{
ms_log (2, "%s: Error packing data samples\n", msr->sid);
libmseed_memory.free (encoded);
libmseed_memory.free (rawrec);
return -1;
}
packoffset += packsamples * samplesize;
reclen = dataoffset + datalength;
/* Copy encoded data into record */
memcpy (rawrec + dataoffset, encoded, datalength);
/* Update number of samples and data length */
*pMS3FSDH_NUMSAMPLES(rawrec) = HO4u (packsamples, swapflag);
*pMS3FSDH_DATALENGTH(rawrec) = HO2u (datalength, swapflag);
/* Calculate CRC (with CRC field set to 0) and set */
memset (pMS3FSDH_CRC(rawrec), 0, sizeof(uint32_t));
crc = ms_crc32c ((const uint8_t*)rawrec, reclen, 0);
*pMS3FSDH_CRC(rawrec) = HO4u (crc, swapflag);
if (verbose >= 1)
ms_log (0, "%s: Packed %d samples into %d byte record\n", msr->sid, packsamples, reclen);
/* Send record to handler */
record_handler (rawrec, reclen, handlerdata);
totalpackedsamples += packsamples;
if (packedsamples)
*packedsamples = totalpackedsamples;
recordcnt++;
if (totalpackedsamples >= msr->numsamples)
break;
/* Update record start time for next record */
nextstarttime = ms_sampletime (msr->starttime, totalpackedsamples, msr->samprate);
if (ms_nstime2time (nextstarttime, &year, &day, &hour, &min, &sec, &nsec))
{
ms_log (2, "%s: Cannot convert next record starttime: %" PRId64 "\n", msr->sid, nextstarttime);
libmseed_memory.free (rawrec);
return -1;
}
*pMS3FSDH_NSEC (rawrec) = HO4u (nsec, swapflag);
*pMS3FSDH_YEAR (rawrec) = HO2u (year, swapflag);
*pMS3FSDH_DAY (rawrec) = HO2u (day, swapflag);
*pMS3FSDH_HOUR (rawrec) = hour;
*pMS3FSDH_MIN (rawrec) = min;
*pMS3FSDH_SEC (rawrec) = sec;
}
if (verbose >= 2)
ms_log (0, "%s: Packed %" PRId64 " total samples\n", msr->sid, totalpackedsamples);
if (encoded)
libmseed_memory.free (encoded);
libmseed_memory.free (rawrec);
return recordcnt;
} /* End of msr3_pack_mseed3() */
/**********************************************************************/ /**
* @brief Repack a parsed miniSEED record into a version 3 record.
*
* Pack the parsed header into a version 3 header and copy the raw
* encoded data from the original record. The original record must be
* available at the ::MS3Record.record pointer.
*
* This can be used to efficiently convert format versions or modify
* header values without unpacking the data samples.
*
* @param[in] msr ::MS3Record containing record to repack
* @param[out] record Destination buffer for repacked record
* @param[in] recbuflen Length of destination buffer
* @param[in] verbose Controls logging verbosity, 0 is no diagnostic output
*
* @returns record length on success and -1 on error.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
int
msr3_repack_mseed3 (const MS3Record *msr, char *record, uint32_t recbuflen,
int8_t verbose)
{
int dataoffset;
uint32_t origdataoffset;
uint32_t origdatasize;
uint32_t crc;
uint32_t reclen;
int8_t swapflag;
if (!msr || ! record)
{
ms_log (2, "Required argument not defined: 'msr' or 'record'\n");
return -1;
}
if (recbuflen < (uint32_t)(MS3FSDH_LENGTH + msr->extralength))
{
ms_log (2, "%s: Record buffer length (%u) is not large enough for header (%d) and extra (%d)\n",
msr->sid, recbuflen, MS3FSDH_LENGTH, msr->extralength);
return -1;
}
if (msr->samplecnt > UINT32_MAX)
{
ms_log (2, "%s: Too many samples in input record (%" PRId64 " for a single record)\n",
msr->sid, msr->samplecnt);
return -1;
}
/* Pack fixed header and extra headers, returned size is data offset */
dataoffset = msr3_pack_header3 (msr, record, recbuflen, verbose);
if (dataoffset < 0)
{
ms_log (2, "%s: Cannot pack miniSEED version 3 header\n", msr->sid);
return -1;
}
/* Determine encoded data size */
if (msr3_data_bounds (msr, &origdataoffset, &origdatasize))
{
ms_log (2, "%s: Cannot determine original data bounds\n", msr->sid);
return -1;
}
if (recbuflen < (uint32_t)(MS3FSDH_LENGTH + msr->extralength + origdatasize))
{
ms_log (2, "%s: Destination record buffer length (%u) is not large enough for record (%d)\n",
msr->sid, recbuflen, (MS3FSDH_LENGTH + msr->extralength + origdatasize));
return -1;
}
reclen = dataoffset + origdatasize;
/* Copy encoded data into record */
memcpy (record + dataoffset, msr->record + origdataoffset, origdatasize);
/* Check to see if byte swapping is needed, miniSEED 3 is little endian */
swapflag = (ms_bigendianhost ()) ? 1 : 0;
/* Update number of samples and data length */
*pMS3FSDH_NUMSAMPLES(record) = HO4u ((uint32_t)msr->samplecnt, swapflag);
*pMS3FSDH_DATALENGTH(record) = HO2u (origdatasize, swapflag);
/* Calculate CRC (with CRC field set to 0) and set */
memset (pMS3FSDH_CRC(record), 0, sizeof(uint32_t));
crc = ms_crc32c ((const uint8_t*)record, reclen, 0);
*pMS3FSDH_CRC(record) = HO4u (crc, swapflag);
if (verbose >= 1)
ms_log (0, "%s: Repacked %" PRId64 " samples into a %u byte record\n",
msr->sid, msr->samplecnt, reclen);
return reclen;
} /* End of msr3_repack_mseed3() */
/**********************************************************************/ /**
* @brief Pack a miniSEED version 3 header into the specified buffer.
*
* Default values are: record length = 4096, encoding = 11 (Steim2).
* The defaults are triggered when \a msr.reclen and \a msr.encoding
* are set to -1.
*
* @param[in] msr ::MS3Record to pack
* @param[out] record Destination for packed header
* @param[in] recbuflen Length of destination buffer
* @param[in] verbose Controls logging verbosity, 0 is no diagnostic output
*
* @returns the size of the header (fixed and extra) on success, otherwise -1.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
int
msr3_pack_header3 (const MS3Record *msr, char *record, uint32_t recbuflen, int8_t verbose)
{
int extraoffset = 0;
size_t sidlength;
int32_t maxreclen;
int8_t encoding;
int8_t swapflag;
uint16_t year;
uint16_t day;
uint8_t hour;
uint8_t min;
uint8_t sec;
uint32_t nsec;
if (!msr || !record)
{
ms_log (2, "Required argument not defined: 'msr' or 'record'\n");
return -1;
}
/* Use default record length and encoding if needed */
maxreclen = (msr->reclen == -1) ? MS_PACK_DEFAULT_RECLEN : msr->reclen;
encoding = (msr->encoding == -1) ? MS_PACK_DEFAULT_ENCODING : msr->encoding;
if (maxreclen < MINRECLEN || maxreclen > MAXRECLEN)
{
ms_log (2, "%s: Record length is out of range: %d\n", msr->sid, maxreclen);
return -1;
}
sidlength = strlen (msr->sid);
if (recbuflen < (uint32_t)(MS3FSDH_LENGTH + sidlength + msr->extralength))
{
ms_log (2, "%s: Buffer length (%d) is not large enough for fixed header (%d), SID (%"PRIsize_t"), and extra (%d)\n",
msr->sid, maxreclen, MS3FSDH_LENGTH, sidlength, msr->extralength);
return -1;
}
/* Check to see if byte swapping is needed, miniSEED 3 is little endian */
swapflag = (ms_bigendianhost ()) ? 1 : 0;
if (verbose > 2 && swapflag)
ms_log (0, "%s: Byte swapping needed for packing of header\n", msr->sid);
/* Break down start time into individual components */
if (ms_nstime2time (msr->starttime, &year, &day, &hour, &min, &sec, &nsec))
{
ms_log (2, "%s: Cannot convert starttime: %" PRId64 "\n", msr->sid, msr->starttime);
return -1;
}
/* Ensure that SID length fits in format, which uses data type uint8_t */
if (sidlength > 255)
{
ms_log (2, "%s: Source ID too long: %"PRIsize_t" bytes\n", msr->sid, sidlength);
return -1;
}
extraoffset = MS3FSDH_LENGTH + sidlength;
/* Build fixed header */
record[0] = 'M';
record[1] = 'S';
*pMS3FSDH_FORMATVERSION (record) = 3;
*pMS3FSDH_FLAGS (record) = msr->flags;
*pMS3FSDH_NSEC (record) = HO4u (nsec, swapflag);
*pMS3FSDH_YEAR (record) = HO2u (year, swapflag);
*pMS3FSDH_DAY (record) = HO2u (day, swapflag);
*pMS3FSDH_HOUR (record) = hour;
*pMS3FSDH_MIN (record) = min;
*pMS3FSDH_SEC (record) = sec;
*pMS3FSDH_ENCODING (record) = encoding;
/* If rate positive and less than one, convert to period notation */
if (msr->samprate != 0.0 && msr->samprate > 0 && msr->samprate < 1.0)
*pMS3FSDH_SAMPLERATE(record) = HO8f((-1.0 / msr->samprate), swapflag);
else
*pMS3FSDH_SAMPLERATE(record) = HO8f(msr->samprate, swapflag);
*pMS3FSDH_PUBVERSION(record) = msr->pubversion;
*pMS3FSDH_SIDLENGTH(record) = (uint8_t)sidlength;
*pMS3FSDH_EXTRALENGTH(record) = HO2u(msr->extralength, swapflag);
memcpy (pMS3FSDH_SID(record), msr->sid, sidlength);
if (msr->extralength > 0)
memcpy (record + extraoffset, msr->extra, msr->extralength);
return (MS3FSDH_LENGTH + sidlength + msr->extralength);
} /* End of msr3_pack_header3() */
/***************************************************************************
* msr3_pack_mseed2:
*
* Pack data into miniSEED version 2 record(s).
*
* Returns the number of records created on success and -1 on error.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
int
msr3_pack_mseed2 (const MS3Record *msr, void (*record_handler) (char *, int, void *),
void *handlerdata, int64_t *packedsamples,
uint32_t flags, int8_t verbose)
{
char *rawrec = NULL;
char *encoded = NULL; /* Separate encoded data buffer for alignment */
int8_t swapflag;
int32_t reclen;
int8_t encoding;
int dataoffset = 0;
int headerlen;
int content;
int samplesize;
int maxdatabytes;
int maxsamples;
int recordcnt = 0;
int packsamples;
int packoffset;
int64_t totalpackedsamples;
uint16_t datalength;
nstime_t nextstarttime;
uint16_t year;
uint16_t day;
uint8_t hour;
uint8_t min;
uint8_t sec;
uint32_t nsec;
if (!msr)
{
ms_log (2, "Required argument not defined: 'msr'\n");
return -1;
}
if (!record_handler)
{
ms_log (2, "callback record_handler() function pointer not set!\n");
return -1;
}
/* Use default record length and encoding if needed */
reclen = (msr->reclen == -1) ? MS_PACK_DEFAULT_RECLEN : msr->reclen;
encoding = (msr->encoding == -1) ? MS_PACK_DEFAULT_ENCODING : msr->encoding;
if (reclen < 128)
{
ms_log (2, "%s: Record length (%d) is not large enough, must be >= 128 bytes\n",
msr->sid, reclen);
return -1;
}
/* Check that record length is a power of 2.
* Power of two if (X & (X - 1)) == 0 */
if ((reclen & (reclen - 1)) != 0)
{
ms_log (2, "%s: Cannot create miniSEED 2, record length (%d) is not a power of 2\n",
msr->sid, reclen);
return -1;
}
/* Check to see if byte swapping is needed, miniSEED 2 is written big endian */
swapflag = (ms_bigendianhost ()) ? 0 : 1;
/* Allocate space for data record */
rawrec = (char *)libmseed_memory.malloc (reclen);
if (rawrec == NULL)
{
ms_log (2, "%s: Cannot allocate memory\n", msr->sid);
return -1;
}
/* Pack fixed header and extra headers, returned size is data offset */
headerlen = msr3_pack_header2 (msr, rawrec, reclen, verbose);
if (headerlen < 0)
return -1;
/* Short cut: if there are no samples, record packing is complete */
if (msr->numsamples <= 0)
{
/* Set encoding to text for consistency and to reduce expectations */
*pMS2B1000_ENCODING (rawrec + 48) = DE_TEXT;
/* Set empty part of record to zeros */
memset (rawrec + headerlen, 0, reclen - headerlen);
if (verbose >= 1)
ms_log (0, "%s: Packed %d byte record with no payload\n", msr->sid, reclen);
/* Send record to handler */
record_handler (rawrec, reclen, handlerdata);
libmseed_memory.free (rawrec);
if (packedsamples)
*packedsamples = 0;
return 1;
}
samplesize = ms_samplesize (msr->sampletype);
if (!samplesize)
{
ms_log (2, "%s: Unknown sample type '%c'\n", msr->sid, msr->sampletype);
return -1;
}
/* Determine offset to encoded data */
if (encoding == DE_STEIM1 || encoding == DE_STEIM2)
{
dataoffset = 64;
while (dataoffset < headerlen)
dataoffset += 64;
/* Zero memory between blockettes and data if any */
memset (rawrec + headerlen, 0, dataoffset - headerlen);
}
else
{
dataoffset = headerlen;
}
/* Set data offset in header */
*pMS2FSDH_DATAOFFSET(rawrec) = HO2u (dataoffset, swapflag);
/* Determine the max data bytes and sample count */
maxdatabytes = reclen - dataoffset;
if (encoding == DE_STEIM1)
{
maxsamples = (int)(maxdatabytes / 64) * STEIM1_FRAME_MAX_SAMPLES;
}
else if (encoding == DE_STEIM2)
{
maxsamples = (int)(maxdatabytes / 64) * STEIM2_FRAME_MAX_SAMPLES;
}
else
{
maxsamples = maxdatabytes / samplesize;
}
/* Allocate space for encoded data separately for alignment */
if (msr->numsamples > 0)
{
encoded = (char *)libmseed_memory.malloc (maxdatabytes);
if (encoded == NULL)
{
ms_log (2, "%s: Cannot allocate memory\n", msr->sid);
libmseed_memory.free (rawrec);
return -1;
}
}
/* Pack samples into records */
totalpackedsamples = 0;
packoffset = 0;
if (packedsamples)
*packedsamples = 0;
while ((msr->numsamples - totalpackedsamples) > maxsamples || flags & MSF_FLUSHDATA)
{
packsamples = msr_pack_data (encoded,
(char *)msr->datasamples + packoffset,
(int)(msr->numsamples - totalpackedsamples), maxdatabytes,
msr->sampletype, encoding, swapflag,
&datalength, msr->sid, verbose);
if (packsamples < 0)
{
ms_log (2, "%s: Error packing data samples\n", msr->sid);
libmseed_memory.free (encoded);
libmseed_memory.free (rawrec);
return -1;
}
packoffset += packsamples * samplesize;
/* Copy encoded data into record */
memcpy (rawrec + dataoffset, encoded, datalength);
/* Update number of samples */
*pMS2FSDH_NUMSAMPLES(rawrec) = HO2u (packsamples, swapflag);
/* Zero any space between encoded data and end of record */
content = dataoffset + datalength;
if (content < reclen)
memset (rawrec + content, 0, reclen - content);
if (verbose >= 1)
ms_log (0, "%s: Packed %d samples into %d byte record\n", msr->sid, packsamples, reclen);
/* Send record to handler */
record_handler (rawrec, reclen, handlerdata);
totalpackedsamples += packsamples;
if (packedsamples)
*packedsamples = totalpackedsamples;
recordcnt++;
if (totalpackedsamples >= msr->numsamples)
break;
/* Update record start time for next record */
nextstarttime = ms_sampletime (msr->starttime, totalpackedsamples, msr->samprate);
if (ms_nstime2time (nextstarttime, &year, &day, &hour, &min, &sec, &nsec))
{
ms_log (2, "%s: Cannot convert next record starttime: %" PRId64 "\n",
msr->sid, nextstarttime);
libmseed_memory.free (rawrec);
return -1;
}
*pMS2FSDH_YEAR (rawrec) = HO2u (year, swapflag);
*pMS2FSDH_DAY (rawrec) = HO2u (day, swapflag);
*pMS2FSDH_HOUR (rawrec) = hour;
*pMS2FSDH_MIN (rawrec) = min;
*pMS2FSDH_SEC (rawrec) = sec;
*pMS2FSDH_FSEC (rawrec) = HO2u ((nsec / 100000), swapflag);
}
if (verbose >= 2)
ms_log (0, "%s: Packed %" PRId64 " total samples\n", msr->sid, totalpackedsamples);
if (encoded)
libmseed_memory.free (encoded);
libmseed_memory.free (rawrec);
return recordcnt;
} /* End of msr3_pack_mseed2() */
/**********************************************************************/ /**
* @brief Pack a miniSEED version 2 header into the specified buffer.
*
* Default values are: record length = 4096, encoding = 11 (Steim2).
* The defaults are triggered when \a msr.reclen and \a msr.encoding
* are set to -1.
*
* @param[in] msr ::MS3Record to pack
* @param[out] record Destination for packed header
* @param[in] recbuflen Length of destination buffer
* @param[in] verbose Controls logging verbosity, 0 is no diagnostic output
*
* @returns the size of the header (fixed and blockettes) on success, otherwise -1.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
int
msr3_pack_header2 (const MS3Record *msr, char *record, uint32_t recbuflen, int8_t verbose)
{
int written = 0;
int8_t swapflag;
int32_t reclen;
int8_t encoding;
char network[64];
char station[64];
char location[64];
char channel[64];
uint16_t year;
uint16_t day;
uint8_t hour;
uint8_t min;
uint8_t sec;
uint32_t nsec;
uint16_t fsec;
int8_t msec_offset;
int reclenexp = 0;
int reclenfind;
int16_t factor;
int16_t multiplier;
uint16_t *next_blockette = NULL;
yyjson_doc *ehdoc = NULL;
yyjson_val *ehroot = NULL;
yyjson_read_flag flg = YYJSON_READ_NOFLAG;
yyjson_alc alc = {_priv_malloc, _priv_realloc, _priv_free, NULL};
yyjson_read_err err;
yyjson_val *eharr;
yyjson_arr_iter ehiter;
yyjson_val *ehiterval;
yyjson_val *ehval;
const char *header_string;
double header_number;
bool header_boolean;
int blockette_type;
int blockette_length;
if (!msr || !record)
{
ms_log (2, "Required argument not defined: 'msr' or 'record'\n");
return -1;
}
/* Use default record length and encoding if needed */
reclen = (msr->reclen == -1) ? MS_PACK_DEFAULT_RECLEN : msr->reclen;
encoding = (msr->encoding == -1) ? MS_PACK_DEFAULT_ENCODING : msr->encoding;
if (reclen < 128 || reclen > MAXRECLEN)
{
ms_log (2, "%s: Record length is out of range: %d\n", msr->sid, reclen);
return -1;
}
/* Check that record length is a power of 2.
* Power of two if (X & (X - 1)) == 0 */
if ((reclen & (reclen - 1)) != 0)
{
ms_log (2, "%s: Cannot pack miniSEED 2, record length (%d) is not a power of 2\n",
msr->sid, reclen);
return -1;
}
/* Calculate the record length as an exponent of 2 */
for (reclenfind = 1, reclenexp = 1; reclenfind <= MAXRECLEN; reclenexp++)
{
reclenfind *= 2;
if (reclenfind == reclen)
break;
}
/* Parse identifier codes from full identifier */
if (ms_sid2nslc (msr->sid, network, station, location, channel))
{
ms_log (2, "%s: Cannot parse SEED identifier codes from full identifier\n", msr->sid);
return -1;
}
/* Verify that identifier codes will fit into and are appropriate for miniSEED 2 */
if (strlen (network) > 2 || strlen (station) > 5 || strlen (location) > 2 || strlen (channel) != 3)
{
ms_log (2, "%s: Cannot create miniSEED 2 for N,S,L,C codes: %s, %s, %s, %s\n",
msr->sid, network, station, location, channel);
return -1;
}
/* Check to see if byte swapping is needed, miniSEED 2 is written big endian */
swapflag = (ms_bigendianhost ()) ? 0 : 1;
if (verbose > 2 && swapflag)
ms_log (0, "%s: Byte swapping needed for packing of header\n", msr->sid);
/* Break down start time into individual components */
if (ms_nstime2time (msr->starttime, &year, &day, &hour, &min, &sec, &nsec))
{
ms_log (2, "%s: Cannot convert starttime: %" PRId64 "\n", msr->sid, msr->starttime);
return -1;
}
/* Calculate time at fractional 100usec resolution and microsecond offset */
fsec = nsec / 100000;
msec_offset = ((nsec / 1000) - (fsec * 100));
/* Generate factor & multipler representation of sample rate */
if (ms_genfactmult (msr3_sampratehz(msr), &factor, &multiplier))
{
ms_log (2, "%s: Cannot convert sample rate (%g) to factor and multiplier\n", msr->sid, msr->samprate);
return -1;
}
/* Parse extra headers if present */
if (msr->extra && msr->extralength > 0)
{
ehdoc = yyjson_read_opts (msr->extra, msr->extralength, flg, &alc, &err);
if (!ehdoc)
{
ms_log (2, "%s() Cannot parse extra header JSON: %s\n",
__func__, (err.msg) ? err.msg : "Unknown error");
return -1;
}
ehroot = yyjson_doc_get_root (ehdoc);
}
/* Build fixed header */
memcpy (pMS2FSDH_SEQNUM (record), "000000", 6);
if (yyjson_ptr_get_str (ehroot, "/FDSN/DataQuality", &header_string) &&
MS2_ISDATAINDICATOR (header_string[0]))
*pMS2FSDH_DATAQUALITY (record) = header_string[0];
else
*pMS2FSDH_DATAQUALITY (record) = 'D';
*pMS2FSDH_RESERVED (record) = ' ';
ms_strncpopen (pMS2FSDH_STATION (record), station, 5);
ms_strncpopen (pMS2FSDH_LOCATION (record), location, 2);
ms_strncpopen (pMS2FSDH_CHANNEL (record), channel, 3);
ms_strncpopen (pMS2FSDH_NETWORK (record), network, 2);
*pMS2FSDH_YEAR (record) = HO2u (year, swapflag);
*pMS2FSDH_DAY (record) = HO2u (day, swapflag);
*pMS2FSDH_HOUR (record) = hour;
*pMS2FSDH_MIN (record) = min;
*pMS2FSDH_SEC (record) = sec;
*pMS2FSDH_UNUSED (record) = 0;
*pMS2FSDH_FSEC (record) = HO2u (fsec, swapflag);
*pMS2FSDH_NUMSAMPLES (record) = 0;
*pMS2FSDH_SAMPLERATEFACT (record) = HO2d (factor, swapflag);
*pMS2FSDH_SAMPLERATEMULT (record) = HO2d (multiplier, swapflag);
/* Map activity bit flags */
*pMS2FSDH_ACTFLAGS (record) = 0;
if (msr->flags & 0x01) /* Bit 0 */
*pMS2FSDH_ACTFLAGS (record) |= 0x01;
if (yyjson_ptr_get_bool (ehroot, "/FDSN/Event/Begin", &header_boolean) && header_boolean) /* Bit 2 */
*pMS2FSDH_ACTFLAGS (record) |= 0x04;
if (yyjson_ptr_get_bool (ehroot, "/FDSN/Event/End", &header_boolean) && header_boolean) /* Bit 3 */
*pMS2FSDH_ACTFLAGS (record) |= 0x08;
if (yyjson_ptr_get_num (ehroot, "/FDSN/Time/LeapSecond", &header_number))
{
if (header_number > 0) /* Bit 4 */
*pMS2FSDH_ACTFLAGS (record) |= 0x10;
else if (header_number < 0) /* Bit 5 */
*pMS2FSDH_ACTFLAGS (record) |= 0x20;
}
if (yyjson_ptr_get_bool (ehroot, "/FDSN/Event/InProgress", &header_boolean) && header_boolean) /* Bit 6 */
*pMS2FSDH_ACTFLAGS (record) |= 0x40;
/* Map I/O and clock bit flags */
*pMS2FSDH_IOFLAGS (record) = 0;
if (yyjson_ptr_get_bool (ehroot, "/FDSN/Flags/StationVolumeParityError", &header_boolean) && header_boolean) /* Bit 0 */
*pMS2FSDH_IOFLAGS (record) |= 0x01;
if (yyjson_ptr_get_bool (ehroot, "/FDSN/Flags/LongRecordRead", &header_boolean) && header_boolean) /* Bit 1 */
*pMS2FSDH_IOFLAGS (record) |= 0x02;
if (yyjson_ptr_get_bool (ehroot, "/FDSN/Flags/ShortRecordRead", &header_boolean) && header_boolean) /* Bit 2 */
*pMS2FSDH_IOFLAGS (record) |= 0x04;
if (yyjson_ptr_get_bool (ehroot, "/FDSN/Flags/StartOfTimeSeries", &header_boolean) && header_boolean) /* Bit 3 */
*pMS2FSDH_IOFLAGS (record) |= 0x08;
if (yyjson_ptr_get_bool (ehroot, "/FDSN/Flags/EndOfTimeSeries", &header_boolean) && header_boolean) /* Bit 4 */
*pMS2FSDH_IOFLAGS (record) |= 0x10;
if (msr->flags & 0x04) /* Bit 5 */
*pMS2FSDH_IOFLAGS (record) |= 0x20;
/* Map data quality bit flags */
*pMS2FSDH_DQFLAGS (record) = 0;
if (yyjson_ptr_get_bool (ehroot, "/FDSN/Flags/AmplifierSaturation", &header_boolean) && header_boolean) /* Bit 0 */
*pMS2FSDH_DQFLAGS (record) |= 0x01;
if (yyjson_ptr_get_bool (ehroot, "/FDSN/Flags/DigitizerClipping", &header_boolean) && header_boolean) /* Bit 1 */
*pMS2FSDH_DQFLAGS (record) |= 0x02;
if (yyjson_ptr_get_bool (ehroot, "/FDSN/Flags/Spikes", &header_boolean) && header_boolean) /* Bit 2 */
*pMS2FSDH_DQFLAGS (record) |= 0x04;
if (yyjson_ptr_get_bool (ehroot, "/FDSN/Flags/Glitches", &header_boolean) && header_boolean) /* Bit 3 */
*pMS2FSDH_DQFLAGS (record) |= 0x08;
if (yyjson_ptr_get_bool (ehroot, "/FDSN/Flags/MissingData", &header_boolean) && header_boolean) /* Bit 4 */
*pMS2FSDH_DQFLAGS (record) |= 0x10;
if (yyjson_ptr_get_bool (ehroot, "/FDSN/Flags/TelemetrySyncError", &header_boolean) && header_boolean) /* Bit 5 */
*pMS2FSDH_DQFLAGS (record) |= 0x20;
if (yyjson_ptr_get_bool (ehroot, "/FDSN/Flags/FilterCharging", &header_boolean) && header_boolean) /* Bit 6 */
*pMS2FSDH_DQFLAGS (record) |= 0x40;
if (msr->flags & 0x02) /* Bit 7 */
*pMS2FSDH_DQFLAGS (record) |= 0x80;
if (yyjson_ptr_get_num (ehroot, "/FDSN/Time/Correction", &header_number))
{
*pMS2FSDH_TIMECORRECT (record) = HO4d (header_number * 10000, swapflag);
/* Set time correction applied bit in activity flags.
Rationale: V3 records do not allow unapplied time corrections and unapplied
time corrections in V2 records are always applied on read by this library. */
*pMS2FSDH_ACTFLAGS (record) |= 0x02;
}
else
{
*pMS2FSDH_TIMECORRECT (record) = 0;
}
*pMS2FSDH_NUMBLOCKETTES (record) = 1;
*pMS2FSDH_DATAOFFSET (record) = 0;
*pMS2FSDH_BLOCKETTEOFFSET (record) = HO2u (48, swapflag);
written = 48;
/* Add mandatory Blockette 1000 */
next_blockette = pMS2B1000_NEXT (record + written);
*pMS2B1000_TYPE (record + written) = HO2u (1000, swapflag);
*pMS2B1000_NEXT (record + written) = 0;
*pMS2B1000_ENCODING (record + written) = encoding;
*pMS2B1000_BYTEORDER (record + written) = 1;
*pMS2B1000_RECLEN (record + written) = reclenexp;
*pMS2B1000_RESERVED (record + written) = 0;
written += 8;
/* Add Blockette 1001 if microsecond offset or timing quality is present */
if (yyjson_ptr_get_num (ehroot, "/FDSN/Time/Quality", &header_number) || msec_offset)
{
*next_blockette = HO2u ((uint16_t)written, swapflag);
next_blockette = pMS2B1001_NEXT (record + written);
*pMS2FSDH_NUMBLOCKETTES (record) += 1;
*pMS2B1001_TYPE (record + written) = HO2u (1001, swapflag);
*pMS2B1001_NEXT (record + written) = 0;
if (yyjson_ptr_get_num (ehroot, "/FDSN/Time/Quality", &header_number))
*pMS2B1001_TIMINGQUALITY (record + written) = (uint8_t) (header_number + 0.5);
else
*pMS2B1001_TIMINGQUALITY (record + written) = 0;
*pMS2B1001_MICROSECOND (record + written) = msec_offset;
*pMS2B1001_RESERVED (record + written) = 0;
*pMS2B1001_FRAMECOUNT (record + written) = 0;
written += 8;
}
/* Add Blockette 100 if sample rate is not well represented by factor/multiplier */
if (ms_dabs(msr3_sampratehz(msr) - ms_nomsamprate(factor, multiplier)) > 0.0001)
{
*next_blockette = HO2u ((uint16_t)written, swapflag);
next_blockette = pMS2B100_NEXT (record + written);
*pMS2FSDH_NUMBLOCKETTES (record) += 1;
*pMS2B100_TYPE (record + written) = HO2u (100, swapflag);
*pMS2B100_NEXT (record + written) = 0;
*pMS2B100_SAMPRATE (record + written) = HO4f (msr->samprate, swapflag);
*pMS2B100_FLAGS (record + written) = 0;
memset (pMS2B100_RESERVED (record + written), 0, 3);
written += 12;
}
/* Add Blockette 500 for timing execeptions */
if ((eharr = yyjson_ptr_get (ehroot, "/FDSN/Time/Exception")) && yyjson_is_arr (eharr))
{
yyjson_arr_iter_init (eharr, &ehiter);
while ((ehiterval = yyjson_arr_iter_next (&ehiter)))
{
if (!yyjson_is_obj (ehiterval))
continue;
blockette_length = 200;
if ((recbuflen - written) < blockette_length)
{
ms_log (2, "%s: Record length not large enough for B500\n", msr->sid);
yyjson_doc_free (ehdoc);
return -1;
}
*next_blockette = HO2u ((uint16_t)written, swapflag);
next_blockette = pMS2B500_NEXT (record + written);
*pMS2FSDH_NUMBLOCKETTES (record) += 1;
memset (record + written, 0, blockette_length);
*pMS2B500_TYPE (record + written) = HO2u (500, swapflag);
*pMS2B500_NEXT (record + written) = 0;
if ((ehval = yyjson_ptr_get (ehiterval, "/VCOCorrection")) && yyjson_is_num (ehval))
*pMS2B500_VCOCORRECTION (record + written) = HO4f (yyjson_get_num (ehval), swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/Time")) && yyjson_is_str (ehval))
{
uint32_t l_nsec;
uint16_t l_fsec;
int8_t l_msec_offset;
l_nsec = ms_timestr2btime (yyjson_get_str (ehval),
(uint8_t *)pMS2B500_YEAR (record + written),
msr->sid, swapflag);
if (l_nsec == -1)
{
ms_log (2, "%s: Cannot convert B500 time: %s\n", msr->sid, yyjson_get_str (ehval));
yyjson_doc_free (ehdoc);
return -1;
}
/* Calculate time at fractional 100usec resolution and microsecond offset */
l_fsec = l_nsec / 100000;
l_msec_offset = ((l_nsec / 1000) - (l_fsec * 100));
*pMS2B500_MICROSECOND (record + written) = l_msec_offset;
}
if ((ehval = yyjson_ptr_get (ehiterval, "/ReceptionQuality")) && yyjson_is_num (ehval))
*pMS2B500_RECEPTIONQUALITY (record + written) = (uint8_t)yyjson_get_num (ehval);
if ((ehval = yyjson_ptr_get (ehiterval, "/Count")) && yyjson_is_num (ehval))
*pMS2B500_EXCEPTIONCOUNT (record + written) = HO4d (yyjson_get_num (ehval), swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/Type")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B500_EXCEPTIONTYPE (record + written), yyjson_get_str (ehval), 16);
if ((ehval = yyjson_ptr_get (ehroot, "/FDSN/Clock/Model")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B500_CLOCKMODEL (record + written), yyjson_get_str (ehval), 32);
if ((ehval = yyjson_ptr_get (ehiterval, "/ClockStatus")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B500_CLOCKSTATUS (record + written), yyjson_get_str (ehval), 128);
written += blockette_length;
}
} /* End if /FDSN/Time/Exception */
/* Add Blockette 200,201 for event detections */
if ((eharr = yyjson_ptr_get (ehroot, "/FDSN/Event/Detection")) && yyjson_is_arr (eharr))
{
yyjson_arr_iter_init (eharr, &ehiter);
while ((ehiterval = yyjson_arr_iter_next (&ehiter)))
{
if (!yyjson_is_obj (ehiterval))
continue;
/* Determine which detection type: MURDOCK versus the generic type */
if ((ehval = yyjson_ptr_get (ehiterval, "/Type")) && yyjson_is_str (ehval) &&
strncasecmp (yyjson_get_str (ehval), "MURDOCK", 7) == 0)
{
blockette_type = 201;
blockette_length = 60;
}
else
{
blockette_type = 200;
blockette_length = 52;
}
if ((recbuflen - written) < blockette_length )
{
ms_log (2, "%s: Record length not large enough for B%d\n", msr->sid, blockette_type);
yyjson_doc_free (ehdoc);
return -1;
}
/* The initial fields of B200 and B201 are the same */
*next_blockette = HO2u ((uint16_t)written, swapflag);
next_blockette = pMS2B200_NEXT (record + written);
*pMS2FSDH_NUMBLOCKETTES (record) += 1;
memset (record + written, 0, blockette_length);
*pMS2B200_TYPE (record + written) = HO2u (blockette_type, swapflag);
*pMS2B200_NEXT (record + written) = 0;
if ((ehval = yyjson_ptr_get (ehiterval, "/SignalAmplitude")) && yyjson_is_num (ehval))
*pMS2B200_AMPLITUDE (record + written) = HO4f (yyjson_get_num (ehval), swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/SignalPeriod")) && yyjson_is_num (ehval))
*pMS2B200_PERIOD (record + written) = HO4f (yyjson_get_num (ehval), swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/BackgroundEstimate")) && yyjson_is_num (ehval))
*pMS2B200_BACKGROUNDEST (record + written) = HO4f (yyjson_get_num (ehval), swapflag);
/* Determine which wave: DILATATION versus (assumed) COMPRESSION */
if ((ehval = yyjson_ptr_get (ehiterval, "/Wave")))
{
if (yyjson_is_str (ehval) && strncasecmp (yyjson_get_str (ehval), "DILATATION", 10) == 0)
*pMS2B200_FLAGS (record + written) |= 0x01;
}
else if (blockette_type == 200)
{
*pMS2B200_FLAGS (record + written) |= 0x04;
}
if (blockette_type == 200 &&
(ehval = yyjson_ptr_get (ehiterval, "/Units")) && yyjson_is_str (ehval) &&
strncasecmp (yyjson_get_str (ehval), "COUNT", 5) != 0)
*pMS2B200_FLAGS (record + written) |= 0x02;
if ((ehval = yyjson_ptr_get (ehiterval, "/OnsetTime")) && yyjson_is_str (ehval))
{
if (ms_timestr2btime (yyjson_get_str (ehval), (uint8_t *)pMS2B200_YEAR (record + written),
msr->sid, swapflag) == -1)
{
ms_log (2, "%s: Cannot convert B%d time: %s\n", msr->sid, blockette_type, yyjson_get_str (ehval));
yyjson_doc_free (ehdoc);
return -1;
}
}
if (blockette_type == 200)
{
if ((ehval = yyjson_ptr_get (ehiterval, "/Detector")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B200_DETECTOR (record + written), yyjson_get_str (ehval), 24);
}
else /* Blockette 201 */
{
yyjson_val *ehsubarr;
yyjson_arr_iter ehsubiter;
yyjson_val *ehsubiterval;
int idx = 0;
if ((ehsubarr = yyjson_ptr_get (ehiterval, "/MEDSNR")) && yyjson_is_arr (ehsubarr))
{
yyjson_arr_iter_init (ehsubarr, &ehsubiter);
while ((ehsubiterval = yyjson_arr_iter_next (&ehsubiter)))
{
if (!yyjson_is_num (ehsubiterval))
continue;
pMS2B201_MEDSNR (record + written)[idx++] = (uint8_t)yyjson_get_num (ehsubiterval);
}
}
if ((ehval = yyjson_ptr_get (ehiterval, "/MEDLookback")) && yyjson_is_num (ehval))
*pMS2B201_LOOPBACK (record + written) = (uint8_t)yyjson_get_num (ehval);
if ((ehval = yyjson_ptr_get (ehiterval, "/MEDPickAlgorithm")) && yyjson_is_num (ehval))
*pMS2B201_PICKALGORITHM (record + written) = (uint8_t)yyjson_get_num (ehval);
if ((ehval = yyjson_ptr_get (ehiterval, "/Detector")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B201_DETECTOR (record + written), yyjson_get_str (ehval), 24);
}
written += blockette_length;
}
} /* End if /FDSN/Event/Detection */
/* Add Blockette B300, 310, 320, 390, 395 for calibrations */
if ((eharr = yyjson_ptr_get (ehroot, "/FDSN/Calibration/Sequence")) && yyjson_is_arr (eharr))
{
yyjson_arr_iter_init (eharr, &ehiter);
while ((ehiterval = yyjson_arr_iter_next (&ehiter)))
{
if (!yyjson_is_obj (ehiterval))
continue;
/* Determine which calibration type: STEP, SINE, PSEUDORANDOM, GENERIC */
blockette_type = 0;
blockette_length = 0;
if ((ehval = yyjson_ptr_get (ehiterval, "/Type")) && yyjson_is_str (ehval))
{
if (strncasecmp (yyjson_get_str (ehval), "STEP", 4) == 0)
{
blockette_type = 300;
blockette_length = 60;
}
else if (strncasecmp (yyjson_get_str (ehval), "SINE", 4) == 0)
{
blockette_type = 310;
blockette_length = 60;
}
else if (strncasecmp (yyjson_get_str (ehval), "PSEUDORANDOM", 12) == 0)
{
blockette_type = 320;
blockette_length = 64;
}
else if (strncasecmp (yyjson_get_str (ehval), "GENERIC", 7) == 0)
{
blockette_type = 390;
blockette_length = 28;
}
}
else if ((ehval = yyjson_ptr_get (ehiterval, "/EndTime")))
{
blockette_type = 395;
blockette_length = 16;
}
if (!blockette_type || !blockette_length)
{
ms_log (2, "%s: Unknown or unset /FDSN/Calibration/Sequence/Type or /EndTime\n", msr->sid);
yyjson_doc_free (ehdoc);
return -1;
}
if ((recbuflen - written) < blockette_length )
{
ms_log (2, "%s: Record length not large enough for B%d\n", msr->sid, blockette_type);
yyjson_doc_free (ehdoc);
return -1;
}
if (blockette_type == 300 || blockette_type == 310 ||
blockette_type == 320 || blockette_type == 390)
{
/* The initial fields of B300, 310, 320, 390 are the same */
*next_blockette = HO2u ((uint16_t)written, swapflag);
next_blockette = pMS2B300_NEXT (record + written);
*pMS2FSDH_NUMBLOCKETTES (record) += 1;
memset (record + written, 0, blockette_length);
*pMS2B300_TYPE (record + written) = HO2u (blockette_type, swapflag);
*pMS2B300_NEXT (record + written) = 0;
if ((ehval = yyjson_ptr_get (ehiterval, "/BeginTime")) && yyjson_is_str (ehval))
{
if (ms_timestr2btime (yyjson_get_str (ehval), (uint8_t *)pMS2B300_YEAR (record + written),
msr->sid, swapflag) == -1)
{
ms_log (2, "%s: Cannot convert B%d time: %s\n", msr->sid, blockette_type, yyjson_get_str (ehval));
yyjson_doc_free (ehdoc);
return -1;
}
}
if (blockette_type == 300)
{
if ((ehval = yyjson_ptr_get (ehiterval, "/Steps")) && yyjson_is_num (ehval))
*pMS2B300_NUMCALIBRATIONS (record + written) = (uint8_t)yyjson_get_num (ehval);
if ((ehval = yyjson_ptr_get (ehiterval, "/StepFirstPulsePositive")) && yyjson_get_bool (ehval))
*pMS2B300_FLAGS (record + written) |= 0x01;
if ((ehval = yyjson_ptr_get (ehiterval, "/StepAlternateSign")) && yyjson_get_bool (ehval))
*pMS2B300_FLAGS (record + written) |= 0x02;
if ((ehval = yyjson_ptr_get (ehiterval, "/Trigger")) && yyjson_is_str (ehval) &&
strncasecmp (yyjson_get_str (ehval), "AUTOMATIC", 9) == 0)
*pMS2B300_FLAGS (record + written) |= 0x04;
if ((ehval = yyjson_ptr_get (ehiterval, "/Continued")) && yyjson_get_bool (ehval))
*pMS2B300_FLAGS (record + written) |= 0x08;
if ((ehval = yyjson_ptr_get (ehiterval, "/Duration")) && yyjson_is_num (ehval))
*pMS2B300_STEPDURATION (record + written) = HO4u (yyjson_get_num (ehval) * 10000, swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/StepBetween")) && yyjson_is_num (ehval))
*pMS2B300_INTERVALDURATION (record + written) = HO4u (yyjson_get_num (ehval) * 10000, swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/Amplitude")) && yyjson_is_num (ehval))
*pMS2B300_AMPLITUDE (record + written) = HO4f (yyjson_get_num (ehval), swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/InputChannel")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B300_INPUTCHANNEL (record + written), yyjson_get_str (ehval), 3);
if ((ehval = yyjson_ptr_get (ehiterval, "/ReferenceAmplitude")) && yyjson_is_num (ehval))
*pMS2B300_REFERENCEAMPLITUDE (record + written) = HO4u (yyjson_get_num (ehval), swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/Coupling")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B300_COUPLING (record + written), yyjson_get_str (ehval), 12);
if ((ehval = yyjson_ptr_get (ehiterval, "/Rolloff")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B300_ROLLOFF (record + written), yyjson_get_str (ehval), 12);
}
else if (blockette_type == 310)
{
if ((ehval = yyjson_ptr_get (ehiterval, "/Trigger")) && yyjson_is_str (ehval) &&
strncasecmp (yyjson_get_str (ehval), "AUTOMATIC", 9) == 0)
*pMS2B310_FLAGS (record + written) |= 0x04;
if ((ehval = yyjson_ptr_get (ehiterval, "/Continued")) && yyjson_get_bool (ehval))
*pMS2B310_FLAGS (record + written) |= 0x08;
if ((ehval = yyjson_ptr_get (ehiterval, "/AmplitudeRange")) && yyjson_is_str (ehval))
{
if (strncasecmp (yyjson_get_str (ehval), "PEAKTOPEAK", 10) == 0)
*pMS2B310_FLAGS (record + written) |= 0x10;
if (strncasecmp (yyjson_get_str (ehval), "ZEROTOPEAK", 10) == 0)
*pMS2B310_FLAGS (record + written) |= 0x20;
if (strncasecmp (yyjson_get_str (ehval), "RMS", 3) == 0)
*pMS2B310_FLAGS (record + written) |= 0x40;
}
if ((ehval = yyjson_ptr_get (ehiterval, "/Duration")) && yyjson_is_num (ehval))
*pMS2B310_DURATION (record + written) = HO4u (yyjson_get_num (ehval) * 10000, swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/SinePeriod")) && yyjson_is_num (ehval))
*pMS2B310_PERIOD (record + written) = HO4f (yyjson_get_num (ehval), swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/Amplitude")) && yyjson_is_num (ehval))
*pMS2B310_AMPLITUDE (record + written) = HO4f (yyjson_get_num (ehval), swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/InputChannel")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B310_INPUTCHANNEL (record + written), yyjson_get_str (ehval), 3);
if ((ehval = yyjson_ptr_get (ehiterval, "/ReferenceAmplitude")) && yyjson_is_num (ehval))
*pMS2B310_REFERENCEAMPLITUDE (record + written) = HO4u (yyjson_get_num (ehval), swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/Coupling")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B320_COUPLING (record + written), yyjson_get_str (ehval), 12);
if ((ehval = yyjson_ptr_get (ehiterval, "/Rolloff")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B320_ROLLOFF (record + written), yyjson_get_str (ehval), 12);
}
else if (blockette_type == 320)
{
if ((ehval = yyjson_ptr_get (ehiterval, "/Trigger")) && yyjson_is_str (ehval) &&
strncasecmp (yyjson_get_str (ehval), "AUTOMATIC", 9) == 0)
*pMS2B320_FLAGS (record + written) |= 0x04;
if ((ehval = yyjson_ptr_get (ehiterval, "/Continued")) && yyjson_get_bool (ehval))
*pMS2B320_FLAGS (record + written) |= 0x08;
if ((ehval = yyjson_ptr_get (ehiterval, "/AmplitudeRange")) && yyjson_is_str (ehval) &&
strncasecmp (yyjson_get_str (ehval), "RANDOM", 6) == 0)
*pMS2B320_FLAGS (record + written) |= 0x10;
if ((ehval = yyjson_ptr_get (ehiterval, "/Duration")) && yyjson_is_num (ehval))
*pMS2B320_DURATION (record + written) = HO4u (yyjson_get_num (ehval) * 10000, swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/Amplitude")) && yyjson_is_num (ehval))
*pMS2B320_PTPAMPLITUDE (record + written) = HO4f (yyjson_get_num (ehval), swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/InputChannel")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B320_INPUTCHANNEL (record + written), yyjson_get_str (ehval), 3);
if ((ehval = yyjson_ptr_get (ehiterval, "/ReferenceAmplitude")) && yyjson_is_num (ehval))
*pMS2B320_REFERENCEAMPLITUDE (record + written) = HO4u (yyjson_get_num (ehval), swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/Coupling")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B320_COUPLING (record + written), yyjson_get_str (ehval), 12);
if ((ehval = yyjson_ptr_get (ehiterval, "/Rolloff")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B320_ROLLOFF (record + written), yyjson_get_str (ehval), 12);
if ((ehval = yyjson_ptr_get (ehiterval, "/Noise")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B320_NOISETYPE (record + written), yyjson_get_str (ehval), 8);
}
else if (blockette_type == 390)
{
if ((ehval = yyjson_ptr_get (ehiterval, "/Trigger")) && yyjson_is_str (ehval) &&
strncasecmp (yyjson_get_str (ehval), "AUTOMATIC", 9) == 0)
*pMS2B390_FLAGS (record + written) |= 0x04;
if ((ehval = yyjson_ptr_get (ehiterval, "/Continued")) && yyjson_get_bool (ehval))
*pMS2B390_FLAGS (record + written) |= 0x08;
if ((ehval = yyjson_ptr_get (ehiterval, "/Duration")) && yyjson_is_num (ehval))
*pMS2B390_DURATION (record + written) = HO4u (yyjson_get_num (ehval) * 10000, swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/Amplitude")) && yyjson_is_num (ehval))
*pMS2B390_AMPLITUDE (record + written) = HO4f (yyjson_get_num (ehval), swapflag);
if ((ehval = yyjson_ptr_get (ehiterval, "/InputChannel")) && yyjson_is_str (ehval))
ms_strncpopen (pMS2B390_INPUTCHANNEL (record + written), yyjson_get_str (ehval), 3);
}
written += blockette_length;
}
/* Add Blockette 395 if EndTime is included */
if ((ehval = yyjson_ptr_get (ehiterval, "/EndTime")) && yyjson_is_str (ehval))
{
blockette_type = 395;
blockette_length = 16;
if ((recbuflen - written) < blockette_length)
{
ms_log (2, "%s: Record length not large enough for B%d\n", msr->sid, blockette_type);
yyjson_doc_free (ehdoc);
return -1;
}
*next_blockette = HO2u ((uint16_t)written, swapflag);
next_blockette = pMS2B395_NEXT (record + written);
*pMS2FSDH_NUMBLOCKETTES (record) += 1;
memset (record + written, 0, blockette_length);
*pMS2B395_TYPE (record + written) = HO2u (blockette_type, swapflag);
*pMS2B395_NEXT (record + written) = 0;
if (ms_timestr2btime (yyjson_get_str (ehval), (uint8_t *)pMS2B395_YEAR (record + written),
msr->sid, swapflag) == -1)
{
ms_log (2, "%s: Cannot convert B%d time: %s\n", msr->sid, blockette_type, yyjson_get_str (ehval));
yyjson_doc_free (ehdoc);
return -1;
}
written += blockette_length;
}
}
} /* End if /FDSN/Event/Detection */
if (ehdoc)
{
yyjson_doc_free (ehdoc);
}
return written;
} /* End of msr3_pack_header2() */
/************************************************************************
* Pack data samples. The input data samples specified as 'src' will
* be packed with 'encoding' format and placed in 'dest'.
*
* Return number of samples packed on success and a negative on error.
*
* \ref MessageOnError - this function logs a message on error
************************************************************************/
static int
msr_pack_data (void *dest, void *src, int maxsamples, int maxdatabytes,
char sampletype, int8_t encoding, int8_t swapflag,
uint16_t *byteswritten, const char *sid, int8_t verbose)
{
int nsamples;
if (byteswritten)
*byteswritten = 0;
/* Decide if this is a format that we can encode */
switch (encoding)
{
case DE_TEXT:
if (sampletype != 't' && sampletype != 'a')
{
ms_log (2, "%s: Sample type must be text (t) for text encoding not '%c'\n",
sid, sampletype);
return -1;
}
if (verbose > 1)
ms_log (0, "%s: Packing text data\n", sid);
nsamples = msr_encode_text ((char *)src, maxsamples, (char *)dest, maxdatabytes);
if (byteswritten && nsamples > 0)
*byteswritten = nsamples;
break;
case DE_INT16:
if (sampletype != 'i')
{
ms_log (2, "%s: Sample type must be integer (i) for INT16 encoding not '%c'\n",
sid, sampletype);
return -1;
}
if (maxdatabytes < sizeof(int16_t))
{
ms_log (2, "%s: Not enough space in record (%d) for INT16 encoding, need at least %"PRIsize_t" bytes\n",
sid, maxdatabytes, sizeof(int16_t));
return -1;
}
if (verbose > 1)
ms_log (0, "%s: Packing INT16 data samples\n", sid);
nsamples = msr_encode_int16 ((int32_t *)src, maxsamples, (int16_t *)dest, maxdatabytes, swapflag);
if (byteswritten && nsamples > 0)
*byteswritten = nsamples * 2;
break;
case DE_INT32:
if (sampletype != 'i')
{
ms_log (2, "%s: Sample type must be integer (i) for INT32 encoding not '%c'\n",
sid, sampletype);
return -1;
}
if (maxdatabytes < sizeof(int32_t))
{
ms_log (2, "%s: Not enough space in record (%d) for INT32 encoding, need at least %"PRIsize_t" bytes\n",
sid, maxdatabytes, sizeof(int32_t));
return -1;
}
if (verbose > 1)
ms_log (0, "%s: Packing INT32 data samples\n", sid);
nsamples = msr_encode_int32 ((int32_t *)src, maxsamples, (int32_t *)dest, maxdatabytes, swapflag);
if (byteswritten && nsamples > 0)
*byteswritten = nsamples * 4;
break;
case DE_FLOAT32:
if (sampletype != 'f')
{
ms_log (2, "%s: Sample type must be float (f) for FLOAT32 encoding not '%c'\n",
sid, sampletype);
return -1;
}
if (maxdatabytes < sizeof(float))
{
ms_log (2, "%s: Not enough space in record (%d) for FLOAT32 encoding, need at least %"PRIsize_t" bytes\n",
sid, maxdatabytes, sizeof(float));
return -1;
}
if (verbose > 1)
ms_log (0, "%s: Packing FLOAT32 data samples\n", sid);
nsamples = msr_encode_float32 ((float *)src, maxsamples, (float *)dest, maxdatabytes, swapflag);
if (byteswritten && nsamples > 0)
*byteswritten = nsamples * 4;
break;
case DE_FLOAT64:
if (sampletype != 'd')
{
ms_log (2, "%s: Sample type must be double (d) for FLOAT64 encoding not '%c'\n",
sid, sampletype);
return -1;
}
if (maxdatabytes < sizeof(double))
{
ms_log (2, "%s: Not enough space in record (%d) for FLOAT64 encoding, need at least %"PRIsize_t" bytes\n",
sid, maxdatabytes, sizeof(double));
return -1;
}
if (verbose > 1)
ms_log (0, "%s: Packing FLOAT64 data samples\n", sid);
nsamples = msr_encode_float64 ((double *)src, maxsamples, (double *)dest, maxdatabytes, swapflag);
if (byteswritten && nsamples > 0)
*byteswritten = nsamples * 8;
break;
case DE_STEIM1:
if (sampletype != 'i')
{
ms_log (2, "%s: Sample type must be integer (i) for Steim1 compression not '%c'\n",
sid, sampletype);
return -1;
}
if (maxdatabytes < 64)
{
ms_log (2, "%s: Not enough space in record (%d) for STEIM1 encoding, need at least 64 bytes\n",
sid, maxdatabytes);
return -1;
}
if (verbose > 1)
ms_log (0, "%s: Packing Steim1 data frames\n", sid);
/* Always big endian Steim1 */
swapflag = (ms_bigendianhost()) ? 0 : 1;
nsamples = msr_encode_steim1 ((int32_t *)src, maxsamples, (int32_t *)dest, maxdatabytes, 0, byteswritten, swapflag);
break;
case DE_STEIM2:
if (sampletype != 'i')
{
ms_log (2, "%s: Sample type must be integer (i) for Steim2 compression not '%c'\n",
sid, sampletype);
return -1;
}
if (maxdatabytes < 64)
{
ms_log (2, "%s: Not enough space in record (%d) for STEIM2 encoding, need at least 64 bytes\n",
sid, maxdatabytes);
return -1;
}
if (verbose > 1)
ms_log (0, "%s: Packing Steim2 data frames\n", sid);
/* Always big endian Steim2 */
swapflag = (ms_bigendianhost()) ? 0 : 1;
nsamples = msr_encode_steim2 ((int32_t *)src, maxsamples, (int32_t *)dest, maxdatabytes, 0, byteswritten, sid, swapflag);
break;
default:
ms_log (2, "%s: Unable to pack format %d\n", sid, encoding);
return -1;
}
return nsamples;
} /* End of msr_pack_data() */
/***************************************************************************
* ms_ratapprox:
*
* Find an approximate rational number for a real through continued
* fraction expansion. Given a double precsion 'real' find a
* numerator (num) and denominator (den) whose absolute values are not
* larger than 'maxval' while trying to reach a specified 'precision'.
*
* Returns the number of iterations performed.
***************************************************************************/
static int
ms_ratapprox (double real, int *num, int *den, int maxval, double precision)
{
double realj, preal;
char pos;
int pnum, pden;
int iterations = 1;
int Aj1, Aj2, Bj1, Bj2;
int bj = 0;
int Aj = 0;
int Bj = 1;
if (real >= 0.0)
{
pos = 1;
realj = real;
}
else
{
pos = 0;
realj = -real;
}
preal = realj;
bj = (int)(realj + precision);
realj = 1 / (realj - bj);
Aj = bj;
Aj1 = 1;
Bj = 1;
Bj1 = 0;
*num = pnum = Aj;
*den = pden = Bj;
if (!pos)
*num = -*num;
while (ms_dabs (preal - (double)Aj / (double)Bj) > precision &&
Aj < maxval && Bj < maxval)
{
Aj2 = Aj1;
Aj1 = Aj;
Bj2 = Bj1;
Bj1 = Bj;
bj = (int)(realj + precision);
realj = 1 / (realj - bj);
Aj = bj * Aj1 + Aj2;
Bj = bj * Bj1 + Bj2;
*num = pnum;
*den = pden;
if (!pos)
*num = -*num;
pnum = Aj;
pden = Bj;
iterations++;
}
if (pnum < maxval && pden < maxval)
{
*num = pnum;
*den = pden;
if (!pos)
*num = -*num;
}
return iterations;
}
/***************************************************************************
* ms_rsqrt64:
*
* An optimized reciprocal square root calculation from:
* Matthew Robertson (2012). "A Brief History of InvSqrt"
* https://cs.uwaterloo.ca/~m32rober/rsqrt.pdf
*
* Further reference and description:
* https://en.wikipedia.org/wiki/Fast_inverse_square_root
*
* Modifications:
* Add 2 more iterations of Newton's method to increase accuracy,
* specifically for large values.
* Use memcpy instead of assignment through differing pointer types.
*
* Returns 0 if the host is little endian, otherwise 1.
***************************************************************************/
static double
ms_rsqrt64 (double val)
{
uint64_t i;
double x2;
double y;
x2 = val * 0.5;
y = val;
memcpy (&i, &y, sizeof (i));
i = 0x5fe6eb50c7b537a9ULL - (i >> 1);
memcpy (&y, &i, sizeof (y));
y = y * (1.5 - (x2 * y * y));
y = y * (1.5 - (x2 * y * y));
y = y * (1.5 - (x2 * y * y));
return y;
} /* End of ms_rsqrt64() */
/***************************************************************************
* ms_reduce_rate:
*
* Reduce the specified sample rate into two "factors" (in some cases
* the second factor is actually a divisor).
*
* Integer rates between 1 and 32767 can be represented exactly.
*
* Integer rates higher than 32767 will be matched as closely as
* possible with the deviation becoming larger as the integers reach
* (32767 * 32767).
*
* Non-integer rates between 32767.0 and 1.0/32767.0 are represented
* exactly when possible and approximated otherwise.
*
* Non-integer rates greater than 32767 or less than 1/32767 are not supported.
*
* Returns 0 on success and -1 on error.
***************************************************************************/
static int
ms_reduce_rate (double samprate, int16_t *factor1, int16_t *factor2)
{
int num;
int den;
int32_t intsamprate = (int32_t) (samprate + 0.5);
int32_t searchfactor1;
int32_t searchfactor2;
int32_t closestfactor;
int32_t closestdiff;
int32_t diff;
/* Handle case of integer sample values. */
if (ms_dabs (samprate - intsamprate) < 0.0000001)
{
/* If integer sample rate is less than range of 16-bit int set it directly */
if (intsamprate <= 32767)
{
*factor1 = intsamprate;
*factor2 = 1;
return 0;
}
/* If integer sample rate is within the maximum possible nominal rate */
else if (intsamprate <= (32767 * 32767))
{
/* Determine the closest factors that represent the sample rate.
* The approximation gets worse as the values increase. */
searchfactor1 = (int)(1.0 / ms_rsqrt64 (samprate));
closestdiff = searchfactor1;
closestfactor = searchfactor1;
while ((intsamprate % searchfactor1) != 0)
{
searchfactor1 -= 1;
/* Track the factor that generates the closest match */
searchfactor2 = intsamprate / searchfactor1;
diff = intsamprate - (searchfactor1 * searchfactor2);
if (diff < closestdiff)
{
closestdiff = diff;
closestfactor = searchfactor1;
}
/* If the next iteration would create a factor beyond the limit
* we accept the closest factor */
if ((intsamprate / (searchfactor1 - 1)) > 32767)
{
searchfactor1 = closestfactor;
break;
}
}
searchfactor2 = intsamprate / searchfactor1;
if (searchfactor1 <= 32767 && searchfactor2 <= 32767)
{
*factor1 = searchfactor1;
*factor2 = searchfactor2;
return 0;
}
}
}
/* Handle case of non-integer less than 16-bit int range */
else if (samprate <= 32767.0)
{
/* For samples/seconds, determine, potentially approximate, numerator and denomiator */
ms_ratapprox (samprate, &num, &den, 32767, 1e-8);
/* Negate the factor2 to denote a division operation */
*factor1 = (int16_t)num;
*factor2 = (int16_t)-den;
return 0;
}
return -1;
} /* End of ms_reduce_rate() */
/***************************************************************************
* ms_genfactmult:
*
* Generate an appropriate SEED sample rate factor and multiplier from
* a double precision sample rate.
*
* If the samplerate > 0.0 it is expected to be a rate in SAMPLES/SECOND.
* If the samplerate < 0.0 it is expected to be a period in SECONDS/SAMPLE.
*
* Results use SAMPLES/SECOND notation when sample rate >= 1.0
* Results use SECONDS/SAMPLE notation when samles rates < 1.0
*
* Returns 0 on success and -1 on error or calculation not possible.
***************************************************************************/
static int
ms_genfactmult (double samprate, int16_t *factor, int16_t *multiplier)
{
int16_t factor1;
int16_t factor2;
if (!factor || !multiplier)
return -1;
/* Convert sample period to sample rate */
if (samprate < 0.0)
samprate = -1.0 / samprate;
/* Handle special case of zero */
if (samprate == 0.0)
{
*factor = 0;
*multiplier = 0;
return 0;
}
/* Handle sample rates >= 1.0 with the SAMPLES/SECOND representation */
else if (samprate >= 1.0)
{
if (ms_reduce_rate (samprate, &factor1, &factor2) == 0)
{
*factor = factor1;
*multiplier = factor2;
return 0;
}
}
/* Handle sample rates < 1 with the SECONDS/SAMPLE representation */
else
{
/* Reduce rate as a sample period and invert factor/multiplier */
if (ms_reduce_rate (1.0 / samprate, &factor1, &factor2) == 0)
{
*factor = -factor1;
*multiplier = -factor2;
return 0;
}
}
return -1;
} /* End of ms_genfactmult() */
/***************************************************************************
* Convenience function to convert a month-day time string to a SEED
* 2.x "BTIME" structure.
*
* The 10-byte BTIME structure layout:
*
* Value Type Offset Description
* year uint16_t 0 Four digit year (e.g. 1987)
* day uint16_t 2 Day of year (Jan 1st is 1)
* hour uint8_t 4 Hour (0 - 23)
* min uint8_t 5 Minute (0 - 59)
* sec uint8_t 6 Second (0 - 59, 60 for leap seconds)
* unused uint8_t 7 Unused, included for alignment
* fract uint16_t 8 0.0001 seconds, i.e. 1/10ths of milliseconds (0—9999)
*
* Return nanoseconds on success and -1 on error.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
static inline uint32_t
ms_timestr2btime (const char *timestr, uint8_t *btime, const char *sid, int8_t swapflag)
{
uint16_t year;
uint16_t day;
uint8_t hour;
uint8_t min;
uint8_t sec;
uint32_t nsec;
nstime_t nstime;
if (!timestr || !btime)
{
ms_log (2, "Required argument not defined: 'timestr' or 'btime'\n");
return -1;
}
if ((nstime = ms_timestr2nstime (timestr)) == NSTERROR)
return -1;
if (ms_nstime2time (nstime, &year, &day, &hour, &min, &sec, &nsec))
return -1;
*((uint16_t *)(btime)) = HO2u (year, swapflag);
*((uint16_t *)(btime + 2)) = HO2u (day, swapflag);
*((uint8_t *)(btime + 4)) = hour;
*((uint8_t *)(btime + 5)) = min;
*((uint8_t *)(btime + 6)) = sec;
*((uint8_t *)(btime + 7)) = 0;
*((uint16_t *)(btime + 8)) = HO2u (nsec / 100000, swapflag);
return nsec;
} /* End of timestr2btime() */