MSR_PACK(3)

✨ MSR_PACK(3) ✨ β€” the libmseed page, bestie edition πŸ’–

okay so. welcome to msr_pack(3) from the Library Functions Manual β€” this is the one where we pack up Mini-SEED records and send them off into the world πŸ¦‹. no cap, it's a whole vibe. let's get into it πŸ’…


🌸 NAME 🌸

msr_pack β€” Packing of Mini-SEED records. that's it. that's the whole job. she packs. she slays. periodt πŸ’–


✨ SYNOPSIS ✨

#include <libmseed.h>

int       msr_pack ( MSRecord *msr,
                     void (*record_handler) (char *, int, void *),
                     void *handlerdata, int64_t *packedsamples,
                     flag flush, flag verbose );

int       msr_pack_header ( MSRecord *msr, flag normalize,
                            flag verbose );

two functions, one header file, and a whole lot of samples about to find their destiny 🌈


πŸ’– DESCRIPTION πŸ’–

so msr_pack creates (packs) Mini-SEED data records. think of it like this: the record header values sitting in an MSRecord data structure, msr, are your template, and she takes the common header fields and blockettes and packs them into a record header πŸŽ€. a Blockette 1000 will be added if one isn't already present in the template β€” she's not gonna leave you hanging like that.

the data samples living at MSRecord.datasamples get packed in the encoding format that the MSRecord.encoding field says. and here's the important bit, bestie: the MSRecord.datasamples array and the MSRecord.numsamples value will not be changed by this routine. nope. untouched. it is the responsibility of the calling routine to adjust the data buffer if desired β€” that's on you, not her πŸ™ˆ. this routine will modify the start time and sequence number of the MSRecord template as it packs records though, so heads up.

the key characteristics of a data record β€” the quality indicator, record length, encoding format and byte order of packed records β€” come from MSRecord.dataquality, MSRecord.reclen, MSRecord.encoding and MSRecord.byteorder respectively. and if you leave them at their "idk" values, defaults kick in: default values for these quantities will be used when the indicator is 0, or the reclen, encoding or byteorder are -1. the defaults are:

  • dataquality = 'D'
  • reclen = 4096 bytes
  • encoding = 11 (Steim2)
  • byteorder = 1 (MSBF or big-endian)

iconic defaults honestly ⭐

MSRecord.dataquality should be either 'D', 'R', 'Q' or 'M'. those are the options. pick one. πŸ’…

MSRecord.reclen should be set to the desired data record length in bytes, and it must be expressible as 2 raised to the power of X where X is between (and including) 8 to 20. so like. powers of two only. math is non-negotiable, sorry babe 😭

MSRecord.encoding should be set to one of the following supported Mini-SEED data encoding formats:

  • DE_ASCII (0)
  • DE_INT16 (1)
  • DE_INT32 (3)
  • DE_FLOAT32 (4)
  • DE_FLOAT64 (5)
  • DE_STEIM1 (10)
  • DE_STEIM2 (11)

the encoding aliases are defined in libmseed.h. iykyk 🫢

MSRecord.sampletype should indicate the sample type as either:

  • 'a' (ASCII)
  • 'i' (32-bit integers)
  • 'f' (32-bit floats)
  • 'd' (64-bit doubles)

and the encoding format must be appropriate for the sample type. for example, Steim compression and integer encoding formats require integer samples, and float encoding formats require the appropriate size floats as input. as a counter example: float samples cannot be packed using Steim compression or integer encoding formats. like, it will not work. do not try it. mismatch = chaos πŸ’€

MSRecord.byteorder must be either 0 (LSBF or little-endian) or 1 (MBF or big-endian). two choices, that's it.

okay now the fun part: every single time a complete record is packed, it gets passed to the record_handler() function, which expects three arguments:

  1. a char * to the record buffer
  2. the length of the record in bytes
  3. a void pointer supplied by the caller

it is the responsibility of record_handler() to process the record β€” the memory will be re-used or freed when record_handler() returns, so don't go hoarding it πŸ™ˆ. and listen: this function pointer is required. there is no other way to access the packed records. none. zero. she said what she said πŸ‘‘

the handlerdata pointer will be passed as the 3rd argument to record_handler(). this lets the caller optionally pass data directly to the record_handler(). very demure, very mindful 🌸

the integer pointed to by packedsamples will be set to the total number of samples packed, if it's not NULL. free real estate πŸ“Š

if the flush flag is not zero, all of the data will be packed into records. otherwise, records will only be packed while there are enough data samples to completely fill a record. so flush is basically the "okay wrap it up" button ✨

the verbose flag controls verbosity β€” a value of zero will result in no diagnostic output. quiet mode. stealth mode. ninja mode 🧚


now msr_pack_header packs header information β€” fixed section and blockettes β€” from a MSRecord structure into the Mini-SEED record at MSRecord.record. this is useful for re-packing record headers after modification. glow-up for your headers πŸ’…

the normalize flag controls whether msr_normalize_header() is called before the header is packed. normalizing updates the SEED structures associated with the MSRecord with values using the MSRecord base members (e.g. MSRecord.network, MSRecord.samplerate, etc.). normally this should be set to true (1) unless the associated SEED structures have been directly modified. so like, default to 1, but if you've been in there messing with the SEED structs yourself, you know what you did πŸ™ˆ

the verbose flag controls verbosity β€” a value of zero will result in no diagnostic output. same deal as before. πŸ’œ


πŸ”₯ PACKING OVERRIDES πŸ”₯

the following macros and environment variables affect the packing of Mini-SEED. yes, this is the override section. she's got range.

Macros:

  • MS_PACKHEADERBYTEORDER(X)
  • MS_PACKDATABYTEORDER(X)

Environment variables:

  • PACK_HEADER_BYTEORDER
  • PACK_DATA_BYTEORDER

these macros and environment variables force the byte order of the header and data respectively. they could be set to either 0 (little endian) or 1 (big endian). normally the byte order of the header and data is determined by the byteorder flag of the MSRecord β€” this capability is included to support any combination of byte orders in a generalized way. so if you need header and data to disagree on endianness, this is your moment, main character 🌈


🧚 COMPRESSION HISTORY 🧚

when the encoding format is Steim 1 or 2 compression, contiguous records will be created including compression history. put simply β€” and bestie i am putting it simply β€” this means that the first difference in the compression series will be the difference between the first sample of the current record and the last sample of the previous record. so the records know about each other. they're connected. it's giving found family 🫢

for the first record in a series (no previous record), a so-called cold start, the first difference will be zero.

the compression history can be seeded by allocating the StreamState struct for the MSRecord and setting the lastintsample member to the integer sample value that preceded the first sample in the current series, and setting the comphistory flag to true (1). seed it, queen 🌱


⭐ RETURN VALUES ⭐

  • msr_pack returns the number records created on success and -1 on error.
  • msr_pack_header returns the header length in bytes on success and -1 on error.

short, sweet, and -1 means something went wrong. that's the whole tea πŸ“


πŸ’€ EXAMPLE πŸ’€

skeleton code for creating (packing) Mini-SEED records with msr_pack(3):

static void record_handler (char *record, int reclen, void *srcname) {
  if ( fwrite(record, reclen, 1, outfile) != 1 )
    {
      ms_log (2, "Error writing %s to output file0, (char *)srcname);
    }
}

main() {
  int64_t psamples;
  int precords;
  MSRecord *msr;
  char srcname[50];

  msr = msr_init (NULL);

  /* Populate MSRecord values */
  strcpy (msr->network, "XX");
  strcpy (msr->station, "TEST");
  strcpy (msr->channel, "BHE");
  msr->starttime = ms_seedtimestr2hptime ("2004,350,00:00:00.00");
  msr->samprate = 40.0;
  msr->reclen = 4096;         /* 4096 byte record length */
  msr->encoding = DE_STEIM2;  /* Steim 2 compression */
  msr->byteorder = 1;         /* big endian byte order */

  msr->datasamples = dataptr; /* pointer to 32-bit integer data samples */
  msr->numsamples = 1234;
  msr->sampletype = 'i';      /* declare type to be 32-bit integers */

  msr_srcname (msr, srcname, 0);

  /* Pack the record(s) */
  precords = msr_pack (msr, &record_handler, srcname, &psamples, 1, verbose);

  ms_log (0, "Packed %"PRId64" samples into %d records0,
             psamples, precords);

  msr_free (&msr);
}

there she is. the whole flow: init, populate, pack, log, free. chef's kiss πŸ‘‘


🌈 SEE ALSO 🌈

go read those too, bestie, they're all in the family πŸ’œ


πŸŽ€ AUTHOR πŸŽ€

Chad Trabant IRIS Data Management Center


and that's the page!! πŸ’– this was Libmseed API, dated 2013/05/17 β€” yeah she's vintage, she's classic, she's still absolutely eating. mwah, love u, go pack some records βœ¨πŸ¦‹πŸ’