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
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() */
|