Skip to content

File lsl_cpp.h

File List > include > lsl_cpp.h

Go to the documentation of this file

#ifndef LSL_CPP_H
#define LSL_CPP_H

#include <memory>
#include <stdexcept>
#include <string>
#include <vector>

extern "C" {
#include "lsl_c.h"
}

namespace lsl {
int32_t check_error(int32_t ec);

const double IRREGULAR_RATE = 0.0;

const double DEDUCED_TIMESTAMP = -1.0;

const double FOREVER = 32000000.0;

enum channel_format_t {
    cf_float32 = 1,
    cf_double64 = 2,
    cf_string = 3,
    cf_int32 = 4,
    cf_int16 = 5,
    cf_int8 = 6,
    cf_int64 = 7,
    cf_undefined = 0
};

enum processing_options_t {
    post_none = 0,
    post_clocksync = 1,
    post_dejitter = 2,
    post_monotonize = 4,
    post_threadsafe = 8,
    post_ALL = 1 | 2 | 4 | 8
};

inline int32_t protocol_version() { return lsl_protocol_version(); }

inline int32_t library_version() { return lsl_library_version(); }

inline const char *library_info() { return lsl_library_info(); }

inline const char *base_version() { return lsl_base_version(); }

inline const char *security_version() { return lsl_security_version(); }

inline const char *full_version() { return lsl_full_version(); }

inline bool is_secure_build() { return lsl_is_secure_build() != 0; }

inline bool local_security_enabled() { return lsl_local_security_enabled() != 0; }

inline bool security_is_locked() { return lsl_security_is_locked() != 0; }

inline bool security_unlock(const char *passphrase) { return lsl_security_unlock(passphrase) != 0; }

inline const char *local_security_fingerprint() { return lsl_local_security_fingerprint(); }

inline double local_clock() { return lsl_local_clock(); }



class xml_element;

class stream_info {
public:
    stream_info(const std::string &name, const std::string &type, int32_t channel_count = 1,
        double nominal_srate = IRREGULAR_RATE, channel_format_t channel_format = cf_float32,
        const std::string &source_id = std::string())
        : obj(lsl_create_streaminfo((name.c_str()), (type.c_str()), channel_count, nominal_srate,
              (lsl_channel_format_t)channel_format, (source_id.c_str())), &lsl_destroy_streaminfo) {
        if (obj == nullptr) throw std::invalid_argument(lsl_last_error());
    }

    stream_info(): stream_info("untitled", "", 0, 0, cf_undefined, ""){}

    stream_info(const stream_info &) noexcept = default;
    stream_info(lsl_streaminfo handle) : obj(handle, &lsl_destroy_streaminfo) {}

    stream_info clone() { return stream_info(lsl_copy_streaminfo(obj.get())); }


    // ========================
    // === Core Information ===
    // ========================
    // (these fields are assigned at construction)

    std::string name() const { return lsl_get_name(obj.get()); }

    std::string type() const { return lsl_get_type(obj.get()); }

    int32_t channel_count() const { return lsl_get_channel_count(obj.get()); }

    double nominal_srate() const { return lsl_get_nominal_srate(obj.get()); }

    channel_format_t channel_format() const {
        return static_cast<channel_format_t>(lsl_get_channel_format(obj.get()));
    }

    std::string source_id() const { return lsl_get_source_id(obj.get()); }


    // ======================================
    // === Additional Hosting Information ===
    // ======================================
    // (these fields are implicitly assigned once bound to an outlet/inlet)

    int32_t version() const { return lsl_get_version(obj.get()); }

    double created_at() const { return lsl_get_created_at(obj.get()); }

    std::string uid() const { return lsl_get_uid(obj.get()); }

    std::string session_id() const { return lsl_get_session_id(obj.get()); }

    std::string hostname() const { return lsl_get_hostname(obj.get()); }


    // ============================
    // === Security Information ===
    // ============================

    bool security_enabled() const { return lsl_get_security_enabled(obj.get()) == 1; }

    std::string security_fingerprint() const { return lsl_get_security_fingerprint(obj.get()); }


    // ========================
    // === Data Description ===
    // ========================

    xml_element desc();

    bool matches_query(const char *query) const {
        return lsl_stream_info_matches_query(obj.get(), query);
    }


    // ===============================
    // === Miscellaneous Functions ===
    // ===============================

    std::string as_xml() const {
        char *tmp = lsl_get_xml(obj.get());
        std::string result(tmp);
        lsl_destroy_string(tmp);
        return result;
    }

    int32_t channel_bytes() const { return lsl_get_channel_bytes(obj.get()); }

    int32_t sample_bytes() const { return lsl_get_sample_bytes(obj.get()); }

    std::shared_ptr<lsl_streaminfo_struct_> handle() const { return obj; }

    stream_info &operator=(const stream_info &rhs) {
        if (this != &rhs) obj = stream_info(rhs).handle();
        return *this;
    }

    stream_info(stream_info &&rhs) noexcept = default;

    stream_info &operator=(stream_info &&rhs) noexcept = default;

    static stream_info from_xml(const std::string &xml) {
        return stream_info(lsl_streaminfo_from_xml(xml.c_str()));
    }

private:
    std::shared_ptr<lsl_streaminfo_struct_> obj;
};


// =======================
// ==== Stream Outlet ====
// =======================

class stream_outlet {
public:
    stream_outlet(const stream_info &info, int32_t chunk_size = 0, int32_t max_buffered = 360,
        lsl_transport_options_t flags = transp_default)
        : channel_count(info.channel_count()), sample_rate(info.nominal_srate()),
          obj(lsl_create_outlet_ex(info.handle().get(), chunk_size, max_buffered, flags),
              &lsl_destroy_outlet) {}

    // ========================================
    // === Pushing a sample into the outlet ===
    // ========================================

    template <class T, int32_t N>
    void push_sample(const T data[N], double timestamp = 0.0, bool pushthrough = true) {
        check_numchan(N);
        push_sample(&data[0], timestamp, pushthrough);
    }

    template<typename T>
    void push_sample(
        const std::vector<T> &data, double timestamp = 0.0, bool pushthrough = true) {
        check_numchan(data.size());
        push_sample(data.data(), timestamp, pushthrough);
    }

    void push_sample(const float *data, double timestamp = 0.0, bool pushthrough = true) {
        lsl_push_sample_ftp(obj.get(), (data), timestamp, pushthrough);
    }
    void push_sample(const double *data, double timestamp = 0.0, bool pushthrough = true) {
        lsl_push_sample_dtp(obj.get(), (data), timestamp, pushthrough);
    }
    void push_sample(const int64_t *data, double timestamp = 0.0, bool pushthrough = true) {
        lsl_push_sample_ltp(obj.get(), (data), timestamp, pushthrough);
    }
    void push_sample(const int32_t *data, double timestamp = 0.0, bool pushthrough = true) {
        lsl_push_sample_itp(obj.get(), (data), timestamp, pushthrough);
    }
    void push_sample(const int16_t *data, double timestamp = 0.0, bool pushthrough = true) {
        lsl_push_sample_stp(obj.get(), (data), timestamp, pushthrough);
    }
    void push_sample(const char *data, double timestamp = 0.0, bool pushthrough = true) {
        lsl_push_sample_ctp(obj.get(), (data), timestamp, pushthrough);
    }
    void push_sample(const std::string *data, double timestamp = 0.0, bool pushthrough = true) {
        std::vector<uint32_t> lengths(channel_count);
        std::vector<const char *> pointers(channel_count);
        for (int32_t k = 0; k < channel_count; k++) {
            pointers[k] = data[k].c_str();
            lengths[k] = (uint32_t)data[k].size();
        }
        lsl_push_sample_buftp(obj.get(), pointers.data(), lengths.data(), timestamp, pushthrough);
    }

    template <class T>
    void push_numeric_struct(const T &sample, double timestamp = 0.0, bool pushthrough = true) {
        if (info().sample_bytes() != sizeof(T))
            throw std::runtime_error(
                "Provided object size does not match the stream's sample size.");
        push_numeric_raw((void *)&sample, timestamp, pushthrough);
    }

    void push_numeric_raw(const void *sample, double timestamp = 0.0, bool pushthrough = true) {
        lsl_push_sample_vtp(obj.get(), (sample), timestamp, pushthrough);
    }


    // ===================================================
    // === Pushing an chunk of samples into the outlet ===
    // ===================================================

    template <class T>
    void push_chunk(
        const std::vector<T> &samples, double timestamp = 0.0, bool pushthrough = true) {
        if (!samples.empty()) {
            if (timestamp == 0.0) timestamp = local_clock();
            if (sample_rate != IRREGULAR_RATE)
                timestamp = timestamp - (samples.size() - 1) / sample_rate;
            push_sample(samples[0], timestamp, pushthrough && samples.size() == 1);
            for (std::size_t k = 1; k < samples.size(); k++)
                push_sample(samples[k], DEDUCED_TIMESTAMP, pushthrough && k == samples.size() - 1);
        }
    }

    template <class T>
    void push_chunk(const std::vector<T> &samples, const std::vector<double> &timestamps,
        bool pushthrough = true) {
        for (unsigned k = 0; k < samples.size() - 1; k++)
            push_sample(samples[k], timestamps[k], false);
        if (!samples.empty()) push_sample(samples.back(), timestamps.back(), pushthrough);
    }

    template <class T>
    void push_chunk_numeric_structs(
        const std::vector<T> &samples, double timestamp = 0.0, bool pushthrough = true) {
        if (!samples.empty()) {
            if (timestamp == 0.0) timestamp = local_clock();
            if (sample_rate != IRREGULAR_RATE)
                timestamp = timestamp - (samples.size() - 1) / sample_rate;
            push_numeric_struct(samples[0], timestamp, pushthrough && samples.size() == 1);
            for (std::size_t k = 1; k < samples.size(); k++)
                push_numeric_struct(
                    samples[k], DEDUCED_TIMESTAMP, pushthrough && k == samples.size() - 1);
        }
    }

    template <class T>
    void push_chunk_numeric_structs(const std::vector<T> &samples,
        const std::vector<double> &timestamps, bool pushthrough = true) {
        for (unsigned k = 0; k < samples.size() - 1; k++)
            push_numeric_struct(samples[k], timestamps[k], false);
        if (!samples.empty()) push_numeric_struct(samples.back(), timestamps.back(), pushthrough);
    }

    template<typename T>
    void push_chunk_multiplexed(
        const std::vector<T> &buffer, double timestamp = 0.0, bool pushthrough = true) {
        if (!buffer.empty())
            push_chunk_multiplexed(
                buffer.data(), static_cast<unsigned long>(buffer.size()), timestamp, pushthrough);
    }

    template<typename T>
    void push_chunk_multiplexed(const std::vector<T> &buffer,
        const std::vector<double> &timestamps, bool pushthrough = true) {
        if (!buffer.empty() && !timestamps.empty())
            push_chunk_multiplexed(
                buffer.data(), static_cast<unsigned long>(buffer.size()), timestamps.data(), pushthrough);
    }

    void push_chunk_multiplexed(const float *buffer, std::size_t buffer_elements,
        double timestamp = 0.0, bool pushthrough = true) {
        lsl_push_chunk_ftp(obj.get(), buffer, static_cast<unsigned long>(buffer_elements), timestamp, pushthrough);
    }
    void push_chunk_multiplexed(const double *buffer, std::size_t buffer_elements,
        double timestamp = 0.0, bool pushthrough = true) {
        lsl_push_chunk_dtp(obj.get(), buffer, static_cast<unsigned long>(buffer_elements), timestamp, pushthrough);
    }
    void push_chunk_multiplexed(const int64_t *buffer, std::size_t buffer_elements,
        double timestamp = 0.0, bool pushthrough = true) {
        lsl_push_chunk_ltp(obj.get(), buffer, static_cast<unsigned long>(buffer_elements), timestamp, pushthrough);
    }
    void push_chunk_multiplexed(const int32_t *buffer, std::size_t buffer_elements,
        double timestamp = 0.0, bool pushthrough = true) {
        lsl_push_chunk_itp(obj.get(), buffer, static_cast<unsigned long>(buffer_elements), timestamp, pushthrough);
    }
    void push_chunk_multiplexed(const int16_t *buffer, std::size_t buffer_elements,
        double timestamp = 0.0, bool pushthrough = true) {
        lsl_push_chunk_stp(obj.get(), buffer, static_cast<unsigned long>(buffer_elements), timestamp, pushthrough);
    }
    void push_chunk_multiplexed(const char *buffer, std::size_t buffer_elements,
        double timestamp = 0.0, bool pushthrough = true) {
        lsl_push_chunk_ctp(obj.get(), buffer, static_cast<unsigned long>(buffer_elements), timestamp, pushthrough);
    }
    void push_chunk_multiplexed(const std::string *buffer, std::size_t buffer_elements,
        double timestamp = 0.0, bool pushthrough = true) {
        if (buffer_elements) {
            std::vector<uint32_t> lengths(buffer_elements);
            std::vector<const char *> pointers(buffer_elements);
            for (std::size_t k = 0; k < buffer_elements; k++) {
                pointers[k] = buffer[k].c_str();
                lengths[k] = (uint32_t)buffer[k].size();
            }
            lsl_push_chunk_buftp(obj.get(), pointers.data(), lengths.data(),
                static_cast<unsigned long>(buffer_elements), timestamp, pushthrough);
        }
    }

    void push_chunk_multiplexed(const float *data_buffer, const double *timestamp_buffer,
        std::size_t data_buffer_elements, bool pushthrough = true) {
        lsl_push_chunk_ftnp(obj.get(), data_buffer, static_cast<unsigned long>(data_buffer_elements),
            (timestamp_buffer), pushthrough);
    }
    void push_chunk_multiplexed(const double *data_buffer, const double *timestamp_buffer,
        std::size_t data_buffer_elements, bool pushthrough = true) {
        lsl_push_chunk_dtnp(obj.get(), data_buffer, static_cast<unsigned long>(data_buffer_elements),
            (timestamp_buffer), pushthrough);
    }
    void push_chunk_multiplexed(const int64_t *data_buffer, const double *timestamp_buffer,
        std::size_t data_buffer_elements, bool pushthrough = true) {
        lsl_push_chunk_ltnp(obj.get(), data_buffer, static_cast<unsigned long>(data_buffer_elements),
            (timestamp_buffer), pushthrough);
    }
    void push_chunk_multiplexed(const int32_t *data_buffer, const double *timestamp_buffer,
        std::size_t data_buffer_elements, bool pushthrough = true) {
        lsl_push_chunk_itnp(obj.get(), data_buffer, static_cast<unsigned long>(data_buffer_elements),
            (timestamp_buffer), pushthrough);
    }
    void push_chunk_multiplexed(const int16_t *data_buffer, const double *timestamp_buffer,
        std::size_t data_buffer_elements, bool pushthrough = true) {
        lsl_push_chunk_stnp(obj.get(), data_buffer, static_cast<unsigned long>(data_buffer_elements),
            (timestamp_buffer), pushthrough);
    }
    void push_chunk_multiplexed(const char *data_buffer, const double *timestamp_buffer,
        std::size_t data_buffer_elements, bool pushthrough = true) {
        lsl_push_chunk_ctnp(obj.get(), data_buffer, static_cast<unsigned long>(data_buffer_elements),
            (timestamp_buffer), pushthrough);
    }

    void push_chunk_multiplexed(const std::string *data_buffer, const double *timestamp_buffer,
        std::size_t data_buffer_elements, bool pushthrough = true) {
        if (data_buffer_elements) {
            std::vector<uint32_t> lengths(data_buffer_elements);
            std::vector<const char *> pointers(data_buffer_elements);
            for (std::size_t k = 0; k < data_buffer_elements; k++) {
                pointers[k] = data_buffer[k].c_str();
                lengths[k] = (uint32_t)data_buffer[k].size();
            }
            lsl_push_chunk_buftnp(obj.get(), pointers.data(), lengths.data(),
                static_cast<unsigned long>(data_buffer_elements), timestamp_buffer, pushthrough);
        }
    }


    // ===============================
    // === Miscellaneous Functions ===
    // ===============================

    bool have_consumers() { return lsl_have_consumers(obj.get()) != 0; }

    bool wait_for_consumers(double timeout) { return lsl_wait_for_consumers(obj.get(), timeout) != 0; }

    stream_info info() const { return stream_info(lsl_get_info(obj.get())); }

    std::shared_ptr<lsl_outlet_struct_> handle() { return obj; }

    ~stream_outlet() = default;

    stream_outlet(stream_outlet &&res) noexcept  = default;

    stream_outlet &operator=(stream_outlet &&rhs) noexcept = default;


private:
    // The outlet is a non-copyable object.
    stream_outlet(const stream_outlet &rhs);
    stream_outlet &operator=(const stream_outlet &rhs);

    void check_numchan(std::size_t N) const {
        if (N != static_cast<std::size_t>(channel_count))
            throw std::runtime_error("Provided element count (" + std::to_string(N) +
                                     ") does not match the stream's channel count (" +
                                     std::to_string(channel_count) + '.');
    }

    int32_t channel_count;
    double sample_rate;
    std::shared_ptr<lsl_outlet_struct_> obj;
};


// ===========================
// ==== Resolve Functions ====
// ===========================

inline std::vector<stream_info> resolve_streams(double wait_time = 1.0) {
    lsl_streaminfo buffer[1024];
    int nres = check_error(lsl_resolve_all(buffer, sizeof(buffer), wait_time));
    return std::vector<stream_info>(&buffer[0], &buffer[nres]);
}

inline std::vector<stream_info> resolve_stream(const std::string &prop, const std::string &value,
    int32_t minimum = 1, double timeout = FOREVER) {
    lsl_streaminfo buffer[1024];
    int nres = check_error(
        lsl_resolve_byprop(buffer, sizeof(buffer), prop.c_str(), value.c_str(), minimum, timeout));
    return std::vector<stream_info>(&buffer[0], &buffer[nres]);
}

inline std::vector<stream_info> resolve_stream(
    const std::string &pred, int32_t minimum = 1, double timeout = FOREVER) {
    lsl_streaminfo buffer[1024];
    int nres =
        check_error(lsl_resolve_bypred(buffer, sizeof(buffer), pred.c_str(), minimum, timeout));
    return std::vector<stream_info>(&buffer[0], &buffer[nres]);
}


// ======================
// ==== Stream Inlet ====
// ======================

class stream_inlet {
public:
    stream_inlet(const stream_info &info, int32_t max_buflen = 360, int32_t max_chunklen = 0,
        bool recover = true, lsl_transport_options_t flags = transp_default)
        : channel_count(info.channel_count()),
          obj(lsl_create_inlet_ex(info.handle().get(), max_buflen, max_chunklen, recover, flags),
              &lsl_destroy_inlet) {}

    std::shared_ptr<lsl_inlet_struct_> handle() { return obj; }

    stream_inlet(stream_inlet &&rhs) noexcept = default;
    stream_inlet &operator=(stream_inlet &&rhs) noexcept= default;


    stream_info info(double timeout = FOREVER) {
        int32_t ec = 0;
        lsl_streaminfo res = lsl_get_fullinfo(obj.get(), timeout, &ec);
        check_error(ec);
        return stream_info(res);
    }

    void open_stream(double timeout = FOREVER) {
        int32_t ec = 0;
        lsl_open_stream(obj.get(), timeout, &ec);
        check_error(ec);
    }

    void close_stream() { lsl_close_stream(obj.get()); }

    double time_correction(double timeout = FOREVER) {
        int32_t ec = 0;
        double res = lsl_time_correction(obj.get(), timeout, &ec);
        check_error(ec);
        return res;
    }
    double time_correction(double *remote_time, double *uncertainty, double timeout = FOREVER) {
        int32_t ec = 0;
        double res = lsl_time_correction_ex(obj.get(), remote_time, uncertainty, timeout, &ec);
        check_error(ec);
        return res;
    }

    void set_postprocessing(uint32_t flags = post_ALL) {
        check_error(lsl_set_postprocessing(obj.get(), flags));
    }

    // =======================================
    // === Pulling a sample from the inlet ===
    // =======================================

    template <class T, int N> double pull_sample(T sample[N], double timeout = FOREVER) {
        return pull_sample(&sample[0], N, timeout);
    }

    double pull_sample(std::vector<float> &sample, double timeout = FOREVER) {
        sample.resize(channel_count);
        return pull_sample(&sample[0], (int32_t)sample.size(), timeout);
    }
    double pull_sample(std::vector<double> &sample, double timeout = FOREVER) {
        sample.resize(channel_count);
        return pull_sample(&sample[0], (int32_t)sample.size(), timeout);
    }
    double pull_sample(std::vector<int64_t> &sample, double timeout = FOREVER) {
        sample.resize(channel_count);
        return pull_sample(&sample[0], (int32_t)sample.size(), timeout);
    }
    double pull_sample(std::vector<int32_t> &sample, double timeout = FOREVER) {
        sample.resize(channel_count);
        return pull_sample(&sample[0], (int32_t)sample.size(), timeout);
    }
    double pull_sample(std::vector<int16_t> &sample, double timeout = FOREVER) {
        sample.resize(channel_count);
        return pull_sample(&sample[0], (int32_t)sample.size(), timeout);
    }
    double pull_sample(std::vector<char> &sample, double timeout = FOREVER) {
        sample.resize(channel_count);
        return pull_sample(&sample[0], (int32_t)sample.size(), timeout);
    }
    double pull_sample(std::vector<std::string> &sample, double timeout = FOREVER) {
        sample.resize(channel_count);
        return pull_sample(&sample[0], (int32_t)sample.size(), timeout);
    }

    double pull_sample(float *buffer, int32_t buffer_elements, double timeout = FOREVER) {
        int32_t ec = 0;
        double res = lsl_pull_sample_f(obj.get(), buffer, buffer_elements, timeout, &ec);
        check_error(ec);
        return res;
    }
    double pull_sample(double *buffer, int32_t buffer_elements, double timeout = FOREVER) {
        int32_t ec = 0;
        double res = lsl_pull_sample_d(obj.get(), buffer, buffer_elements, timeout, &ec);
        check_error(ec);
        return res;
    }
    double pull_sample(int64_t *buffer, int32_t buffer_elements, double timeout = FOREVER) {
        int32_t ec = 0;
        double res = lsl_pull_sample_l(obj.get(), buffer, buffer_elements, timeout, &ec);
        check_error(ec);
        return res;
    }
    double pull_sample(int32_t *buffer, int32_t buffer_elements, double timeout = FOREVER) {
        int32_t ec = 0;
        double res = lsl_pull_sample_i(obj.get(), buffer, buffer_elements, timeout, &ec);
        check_error(ec);
        return res;
    }
    double pull_sample(int16_t *buffer, int32_t buffer_elements, double timeout = FOREVER) {
        int32_t ec = 0;
        double res = lsl_pull_sample_s(obj.get(), buffer, buffer_elements, timeout, &ec);
        check_error(ec);
        return res;
    }
    double pull_sample(char *buffer, int32_t buffer_elements, double timeout = FOREVER) {
        int32_t ec = 0;
        double res = lsl_pull_sample_c(obj.get(), buffer, buffer_elements, timeout, &ec);
        check_error(ec);
        return res;
    }
    double pull_sample(std::string *buffer, int32_t buffer_elements, double timeout = FOREVER) {
        int32_t ec = 0;
        if (buffer_elements) {
            std::vector<char *> result_strings(buffer_elements);
            std::vector<uint32_t> result_lengths(buffer_elements);
            double res = lsl_pull_sample_buf(
                obj.get(), result_strings.data(), result_lengths.data(), buffer_elements, timeout, &ec);
            check_error(ec);
            for (int32_t k = 0; k < buffer_elements; k++) {
                buffer[k].assign(result_strings[k], result_lengths[k]);
                lsl_destroy_string(result_strings[k]);
            }
            return res;
        } else
            throw std::runtime_error(
                "Provided element count does not match the stream's channel count.");
    }

    template <class T> double pull_numeric_struct(T &sample, double timeout = FOREVER) {
        return pull_numeric_raw((void *)&sample, sizeof(T), timeout);
    }

    double pull_numeric_raw(void *sample, int32_t buffer_bytes, double timeout = FOREVER) {
        int32_t ec = 0;
        double res = lsl_pull_sample_v(obj.get(), sample, buffer_bytes, timeout, &ec);
        check_error(ec);
        return res;
    }


    // =================================================
    // === Pulling a chunk of samples from the inlet ===
    // =================================================

    template <class T>
    bool pull_chunk(std::vector<std::vector<T>> &chunk, std::vector<double> &timestamps) {
        std::vector<T> sample;
        chunk.clear();
        timestamps.clear();
        while (double ts = pull_sample(sample, 0.0)) {
            chunk.push_back(sample);
            timestamps.push_back(ts);
        }
        return !chunk.empty();
    }

    template <class T> double pull_chunk(std::vector<std::vector<T>> &chunk) {
        double timestamp = 0.0;
        std::vector<T> sample;
        chunk.clear();
        while (double ts = pull_sample(sample, 0.0)) {
            chunk.push_back(sample);
            timestamp = ts;
        }
        return timestamp;
    }

    template <class T> std::vector<std::vector<T>> pull_chunk() {
        std::vector<std::vector<T>> result;
        std::vector<T> sample;
        while (pull_sample(sample, 0.0)) result.push_back(sample);
        return result;
    }

    std::size_t pull_chunk_multiplexed(float *data_buffer, double *timestamp_buffer,
        std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements,
        double timeout = 0.0) {
        int32_t ec = 0;
        std::size_t res = lsl_pull_chunk_f(obj.get(), data_buffer, timestamp_buffer,
            (unsigned long)data_buffer_elements, (unsigned long)timestamp_buffer_elements, timeout,
            &ec);
        check_error(ec);
        return res;
    }
    std::size_t pull_chunk_multiplexed(double *data_buffer, double *timestamp_buffer,
        std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements,
        double timeout = 0.0) {
        int32_t ec = 0;
        std::size_t res = lsl_pull_chunk_d(obj.get(), data_buffer, timestamp_buffer,
            (unsigned long)data_buffer_elements, (unsigned long)timestamp_buffer_elements, timeout,
            &ec);
        check_error(ec);
        return res;
    }
    std::size_t pull_chunk_multiplexed(int64_t *data_buffer, double *timestamp_buffer,
        std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements,
        double timeout = 0.0) {
        int32_t ec = 0;
        std::size_t res = lsl_pull_chunk_l(obj.get(), data_buffer, timestamp_buffer,
            (unsigned long)data_buffer_elements, (unsigned long)timestamp_buffer_elements, timeout,
            &ec);
        check_error(ec);
        return res;
    }
    std::size_t pull_chunk_multiplexed(int32_t *data_buffer, double *timestamp_buffer,
        std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements,
        double timeout = 0.0) {
        int32_t ec = 0;
        std::size_t res = lsl_pull_chunk_i(obj.get(), data_buffer, timestamp_buffer,
            (unsigned long)data_buffer_elements, (unsigned long)timestamp_buffer_elements, timeout,
            &ec);
        check_error(ec);
        return res;
    }
    std::size_t pull_chunk_multiplexed(int16_t *data_buffer, double *timestamp_buffer,
        std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements,
        double timeout = 0.0) {
        int32_t ec = 0;
        std::size_t res = lsl_pull_chunk_s(obj.get(), data_buffer, timestamp_buffer,
            (unsigned long)data_buffer_elements, (unsigned long)timestamp_buffer_elements, timeout,
            &ec);
        check_error(ec);
        return res;
    }
    std::size_t pull_chunk_multiplexed(char *data_buffer, double *timestamp_buffer,
        std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements,
        double timeout = 0.0) {
        int32_t ec = 0;
        std::size_t res = lsl_pull_chunk_c(obj.get(), data_buffer, timestamp_buffer,
            static_cast<unsigned long>(data_buffer_elements), static_cast<unsigned long>(timestamp_buffer_elements), timeout,
            &ec);
        check_error(ec);
        return res;
    }
    std::size_t pull_chunk_multiplexed(std::string *data_buffer, double *timestamp_buffer,
        std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements,
        double timeout = 0.0) {
        int32_t ec = 0;
        if (data_buffer_elements) {
            std::vector<char *> result_strings(data_buffer_elements);
            std::vector<uint32_t> result_lengths(data_buffer_elements);
            std::size_t num = lsl_pull_chunk_buf(obj.get(), result_strings.data(), result_lengths.data(),
                timestamp_buffer, static_cast<unsigned long>(data_buffer_elements),
                static_cast<unsigned long>(timestamp_buffer_elements), timeout, &ec);
            check_error(ec);
            for (std::size_t k = 0; k < num; k++) {
                data_buffer[k].assign(result_strings[k], result_lengths[k]);
                lsl_destroy_string(result_strings[k]);
            }
            return num;
        };
        return 0;
    }

    template <typename T>
    bool pull_chunk_multiplexed(std::vector<T> &chunk, std::vector<double> *timestamps = nullptr,
        double timeout = 0.0, bool append = false) {
        if (!append) {
            chunk.clear();
            if (timestamps) timestamps->clear();
        }
        std::vector<T> sample;
        double ts;
        if ((ts = pull_sample(sample, timeout)) == 0.0) return false;
        chunk.insert(chunk.end(), sample.begin(), sample.end());
        if (timestamps) timestamps->push_back(ts);
        const auto target = samples_available();
        chunk.reserve(chunk.size() + target * this->channel_count);
        if (timestamps) timestamps->reserve(timestamps->size() + target);
        while ((ts = pull_sample(sample, 0.0)) != 0.0) {
#if LSL_CPP11
            chunk.insert(chunk.end(), std::make_move_iterator(sample.begin()),
                std::make_move_iterator(sample.end()));
#else
            chunk.insert(chunk.end(), sample.begin(), sample.end());
#endif
            if (timestamps) timestamps->push_back(ts);
        }
        return true;
    }

    template <class T>
    bool pull_chunk_numeric_structs(std::vector<T> &chunk, std::vector<double> &timestamps) {
        T sample;
        chunk.clear();
        timestamps.clear();
        while (double ts = pull_numeric_struct(sample, 0.0)) {
            chunk.push_back(sample);
            timestamps.push_back(ts);
        }
        return !chunk.empty();
    }

    template <class T> double pull_chunk_numeric_structs(std::vector<T> &chunk) {
        double timestamp = 0.0;
        T sample;
        chunk.clear();
        while (double ts = pull_numeric_struct(sample, 0.0)) {
            chunk.push_back(sample);
            timestamp = ts;
        }
        return timestamp;
    }

    template <class T> std::vector<T> pull_chunk_numeric_structs() {
        std::vector<T> result;
        T sample;
        while (pull_numeric_struct(sample, 0.0)) result.push_back(sample);
        return result;
    }

    std::size_t samples_available() { return lsl_samples_available(obj.get()); }

    uint32_t flush() noexcept { return lsl_inlet_flush(obj.get()); }

    bool was_clock_reset() { return lsl_was_clock_reset(obj.get()) != 0; }

    void smoothing_halftime(float value) { check_error(lsl_smoothing_halftime(obj.get(), value)); }

    int get_channel_count() const { return channel_count; }

private:
    // The inlet is a non-copyable object.
    stream_inlet(const stream_inlet &rhs);
    stream_inlet &operator=(const stream_inlet &rhs);

    int32_t channel_count;
    std::shared_ptr<lsl_inlet_struct_> obj;
};


// =====================
// ==== XML Element ====
// =====================

class xml_element {
public:
    xml_element(lsl_xml_ptr obj = 0) : obj(obj) {}


    // === Tree Navigation ===

    xml_element first_child() const { return lsl_first_child(obj); }

    xml_element last_child() const { return lsl_last_child(obj); }

    xml_element next_sibling() const { return lsl_next_sibling(obj); }

    xml_element previous_sibling() const { return lsl_previous_sibling(obj); }

    xml_element parent() const { return lsl_parent(obj); }


    // === Tree Navigation by Name ===

    xml_element child(const std::string &name) const { return lsl_child(obj, (name.c_str())); }

    xml_element next_sibling(const std::string &name) const {
        return lsl_next_sibling_n(obj, (name.c_str()));
    }

    xml_element previous_sibling(const std::string &name) const {
        return lsl_previous_sibling_n(obj, (name.c_str()));
    }


    // === Content Queries ===

    bool empty() const { return lsl_empty(obj) != 0; }

    bool is_text() const { return lsl_is_text(obj) != 0; }

    const char *name() const { return lsl_name(obj); }

    const char *value() const { return lsl_value(obj); }

    const char *child_value() const { return lsl_child_value(obj); }

    const char *child_value(const std::string &name) const {
        return lsl_child_value_n(obj, (name.c_str()));
    }


    // === Modification ===

    xml_element append_child_value(const std::string &name, const std::string &value) {
        return lsl_append_child_value(obj, (name.c_str()), (value.c_str()));
    }

    xml_element prepend_child_value(const std::string &name, const std::string &value) {
        return lsl_prepend_child_value(obj, (name.c_str()), (value.c_str()));
    }

    bool set_child_value(const std::string &name, const std::string &value) {
        return lsl_set_child_value(obj, (name.c_str()), (value.c_str())) != 0;
    }

    bool set_name(const std::string &rhs) { return lsl_set_name(obj, rhs.c_str()) != 0; }

    bool set_value(const std::string &rhs) { return lsl_set_value(obj, rhs.c_str()) != 0; }

    xml_element append_child(const std::string &name) {
        return lsl_append_child(obj, name.c_str());
    }

    xml_element prepend_child(const std::string &name) {
        return lsl_prepend_child(obj, (name.c_str()));
    }

    xml_element append_copy(const xml_element &e) { return lsl_append_copy(obj, e.obj); }

    xml_element prepend_copy(const xml_element &e) { return lsl_prepend_copy(obj, e.obj); }

    void remove_child(const std::string &name) { lsl_remove_child_n(obj, (name.c_str())); }

    void remove_child(const xml_element &e) { lsl_remove_child(obj, e.obj); }

private:
    lsl_xml_ptr obj;
};

inline xml_element stream_info::desc() { return lsl_get_desc(obj.get()); }


// =============================
// ==== Continuous Resolver ====
// =============================

class continuous_resolver {
public:
    continuous_resolver(double forget_after = 5.0)
        : obj(lsl_create_continuous_resolver(forget_after), &lsl_destroy_continuous_resolver) {}

    continuous_resolver(
        const std::string &prop, const std::string &value, double forget_after = 5.0)
        : obj(lsl_create_continuous_resolver_byprop((prop.c_str()), (value.c_str()), forget_after),
              &lsl_destroy_continuous_resolver) {}

    continuous_resolver(const std::string &pred, double forget_after = 5.0)
        : obj(lsl_create_continuous_resolver_bypred((pred.c_str()), forget_after), &lsl_destroy_continuous_resolver) {}

    std::vector<stream_info> results() {
        lsl_streaminfo buffer[1024];
        return std::vector<stream_info>(
            buffer, buffer + check_error(lsl_resolver_results(obj.get(), buffer, sizeof(buffer))));
    }

    continuous_resolver(continuous_resolver &&rhs) noexcept = default;
    continuous_resolver &operator=(continuous_resolver &&rhs) noexcept = default;

private:
    std::unique_ptr<lsl_continuous_resolver_, void(*)(lsl_continuous_resolver_*)> obj;
};


// ===============================
// ==== Exception Definitions ====
// ===============================

class lost_error : public std::runtime_error {
public:
    explicit lost_error(const std::string &msg) : std::runtime_error(msg) {}
};


class timeout_error : public std::runtime_error {
public:
    explicit timeout_error(const std::string &msg) : std::runtime_error(msg) {}
};

inline int32_t check_error(int32_t ec) {
    if (ec < 0) {
        switch (ec) {
        case lsl_timeout_error: throw timeout_error("The operation has timed out.");
        case lsl_lost_error:
            throw timeout_error(
                "The stream has been lost; to continue reading, you need to re-resolve it.");
        case lsl_argument_error:
            throw std::invalid_argument("An argument was incorrectly specified.");
        case lsl_internal_error: throw std::runtime_error("An internal error has occurred.");
        default: throw std::runtime_error("An unknown error has occurred.");
        }
    }
    return ec;
}

} // namespace lsl

#endif // LSL_CPP_H