Changeset - 6b14ef397a98
[Not reviewed]
gain-offset
0 4 0
Hasan Yavuz ÖZDERYA - 7 years ago 2018-07-22 08:34:09
hy@ozderya.net
some optimization for when no gain or offset is enabled
4 files changed with 81 insertions and 31 deletions:
0 comments (0 inline, 0 general)
src/channelinfomodel.cpp
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "channelinfomodel.h"
 
#include "setting_defines.h"
 

	
 
#define NUMOF_COLORS  (32)
 

	
 
const QColor colors[NUMOF_COLORS] =
 
{
 
    QColor("#ff0056"),
 
    QColor("#7e2dd2"),
 
    QColor("#00ae7e"),
 
    QColor("#fe8900"),
 
    QColor("#ff937e"),
 
    QColor("#6a826c"),
 
    QColor("#ff029d"),
 
    QColor("#00b917"),
 
    QColor("#7a4782"),
 
    QColor("#85a900"),
 
    QColor("#a42400"),
 
    QColor("#683d3b"),
 
    QColor("#bdc6ff"),
 
    QColor("#263400"),
 
    QColor("#bdd393"),
 
    QColor("#d5ff00"),
 
    QColor("#9e008e"),
 
    QColor("#001544"),
 
    QColor("#c28c9f"),
 
    QColor("#ff74a3"),
 
    QColor("#01d0ff"),
 
    QColor("#004754"),
 
    QColor("#e56ffe"),
 
    QColor("#788231"),
 
    QColor("#0e4ca1"),
 
    QColor("#91d0cb"),
 
    QColor("#be9970"),
 
    QColor("#968ae8"),
 
    QColor("#bb8800"),
 
    QColor("#43002c"),
 
    QColor("#deff74"),
 
    QColor("#00ffc6")
 
};
 

	
 
ChannelInfoModel::ChannelInfoModel(unsigned numberOfChannels, QObject* parent) :
 
    QAbstractTableModel(parent)
 
{
 
    _numOfChannels = 0;
 
    setNumOfChannels(numberOfChannels);
 
}
 

	
 
ChannelInfoModel::ChannelInfoModel(const ChannelInfoModel& other) :
 
    ChannelInfoModel(other.rowCount(), other.parent())
 
{
 
    // TODO: why not set (copy) info list directly instead?
 
    for (int i = 0; i < other.rowCount(); i++)
 
    {
 
        setData(index(i, COLUMN_NAME),
 
                other.data(other.index(i, COLUMN_NAME), Qt::EditRole),
 
                Qt::EditRole);
 
        setData(index(i, COLUMN_NAME),
 
                other.data(other.index(i, COLUMN_NAME), Qt::ForegroundRole),
 
                Qt::ForegroundRole);
 

	
 
        setData(index(i, COLUMN_VISIBILITY),
 
                other.data(other.index(i, COLUMN_VISIBILITY), Qt::CheckStateRole),
 
                Qt::CheckStateRole);
 
        // TODO: gain and offset
 

	
 
        setData(index(i, COLUMN_GAIN),
 
                other.data(other.index(i, COLUMN_GAIN), Qt::CheckStateRole),
 
                Qt::CheckStateRole);
 
        setData(index(i, COLUMN_GAIN),
 
                other.data(other.index(i, COLUMN_GAIN), Qt::EditRole),
 
                Qt::EditRole);
 

	
 
        setData(index(i, COLUMN_OFFSET),
 
                other.data(other.index(i, COLUMN_OFFSET), Qt::CheckStateRole),
 
                Qt::CheckStateRole);
 
        setData(index(i, COLUMN_OFFSET),
 
                other.data(other.index(i, COLUMN_OFFSET), Qt::EditRole),
 
                Qt::EditRole);
 
    }
 
}
 

	
 
ChannelInfoModel::ChannelInfoModel(const QStringList& channelNames) :
 
    ChannelInfoModel(channelNames.length(), NULL)
 
{
 
    for (int i = 0; i < channelNames.length(); i++)
 
    {
 
        setData(index(i, COLUMN_NAME), channelNames[i], Qt::EditRole);
 
    }
 
}
 

	
 
ChannelInfoModel::ChannelInfo::ChannelInfo(unsigned index)
 
{
 
    name = tr("Channel %1").arg(index + 1);
 
    visibility = true;
 
    color = colors[index % NUMOF_COLORS];
 
    gain = 1.0;
 
    offset = 0.0;
 
    gainEn = false;
 
    offsetEn = false;
 
}
 

	
 
QString ChannelInfoModel::name(unsigned i) const
 
{
 
    return infos[i].name;
 
}
 

	
 
QColor ChannelInfoModel::color(unsigned i) const
 
{
 
    return infos[i].color;
 
}
 

	
 
bool ChannelInfoModel::isVisible(unsigned i) const
 
{
 
    return infos[i].visibility;
 
}
 

	
 
bool ChannelInfoModel::gainEn (unsigned i) const
 
{
 
    return infos[i].gainEn;
 
}
 

	
 
double ChannelInfoModel::gain (unsigned i) const
 
{
 
    return infos[i].gain;
 
}
 

	
 
bool ChannelInfoModel::offsetEn (unsigned i) const
 
{
 
    return infos[i].offsetEn;
 
}
 

	
 
double ChannelInfoModel::offset (unsigned i) const
 
{
 
    return infos[i].offset;
 
}
 

	
 
QStringList ChannelInfoModel::channelNames() const
 
{
 
    QStringList r;
 
    for (unsigned ci = 0; ci < _numOfChannels; ci++)
 
    {
 
        r << name(ci);
 
    }
 
    return r;
 
}
 

	
 
int ChannelInfoModel::rowCount(const QModelIndex &parent) const
 
{
 
    return _numOfChannels;
 
}
 

	
 
int ChannelInfoModel::columnCount(const QModelIndex & parent) const
 
{
 
    return COLUMN_COUNT;
 
}
 

	
 
Qt::ItemFlags ChannelInfoModel::flags(const QModelIndex &index) const
 
{
 
    if (index.column() == COLUMN_NAME)
 
    {
 
        return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable;
 
    }
 
    else if (index.column() == COLUMN_VISIBILITY)
 
    {
 
        return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable;
 
    }
 
    else if (index.column() == COLUMN_GAIN || index.column() == COLUMN_OFFSET)
 
    {
 
        return Qt::ItemIsEditable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable;
 
    }
 

	
 
    return Qt::NoItemFlags;
 
}
 

	
 
QVariant ChannelInfoModel::data(const QModelIndex &index, int role) const
 
{
 
    // check index
 
    if (index.row() >= (int) _numOfChannels || index.row() < 0)
 
    {
 
        return QVariant();
 
    }
 

	
 
    auto &info = infos[index.row()];
 

	
 
    // get color
 
    if (role == Qt::ForegroundRole)
 
    {
 
        return info.color;
 
    }
 

	
 
    // name
 
    if (index.column() == COLUMN_NAME)
 
    {
 
        if (role == Qt::DisplayRole || role == Qt::EditRole)
 
        {
 
            return QVariant(info.name);
 
        }
 
    } // visibility
 
    else if (index.column() == COLUMN_VISIBILITY)
 
    {
 
        if (role == Qt::CheckStateRole)
 
        {
 
            bool visible = info.visibility;
 
            return visible ? Qt::Checked : Qt::Unchecked;
 
        }
 
    } // gain
 
    else if (index.column() == COLUMN_GAIN)
 
    {
 
        if (role == Qt::CheckStateRole)
 
        {
 
            return info.gainEn ? Qt::Checked : Qt::Unchecked;
 
        }
 
        else if (role == Qt::DisplayRole || role == Qt::EditRole)
 
        {
 
            return QVariant(info.gain);
 
        }
 
    } // offset
 
    else if (index.column() == COLUMN_OFFSET)
 
    {
 
        if (role == Qt::CheckStateRole)
 
        {
 
            return info.offsetEn ? Qt::Checked : Qt::Unchecked;
 
        }
 
        else if (role == Qt::DisplayRole || role == Qt::EditRole)
 
        {
 
            return QVariant(info.offset);
 
        }
 
    }
 

	
 
    return QVariant();
 
}
 

	
 
QVariant ChannelInfoModel::headerData(int section, Qt::Orientation orientation, int role) const
 
{
 
    if (orientation == Qt::Horizontal)
 
    {
 
        if (role == Qt::DisplayRole)
 
        {
 
            if (section == COLUMN_NAME)
 
            {
 
                return tr("Channel");
 
            }
 
            else if (section == COLUMN_VISIBILITY)
 
            {
 
                return tr("Visible");
 
            }
 
            else if (section == COLUMN_GAIN)
 
            {
 
                return tr("Gain");
 
            }
 
            else if (section == COLUMN_OFFSET)
 
            {
 
                return tr("Offset");
 
            }
 
        }
 
    }
 
    else                        // vertical
 
    {
 
        if (section < (int) _numOfChannels && role == Qt::DisplayRole)
 
        {
 
            return QString::number(section + 1);
 
        }
 
    }
 

	
 
    return QVariant();
 
}
 

	
 
bool ChannelInfoModel::setData(const QModelIndex &index, const QVariant &value, int role)
 
{
 
    // check index
 
    if (index.row() >= (int) _numOfChannels || index.row() < 0)
 
    {
 
        return false;
 
    }
 

	
 
    auto &info = infos[index.row()];
 

	
 
    // set color
 
    if (role == Qt::ForegroundRole)
 
    {
 
        info.color = value.value<QColor>();
 
        emit dataChanged(index, index, QVector<int>({Qt::ForegroundRole}));
 
        return true;
 
    }
 

	
 
    // set name
 
    bool r = false;
 
    if (index.column() == COLUMN_NAME)
 
    {
 
        if (role == Qt::DisplayRole || role == Qt::EditRole)
 
        {
 
            info.name = value.toString();
 
            emit dataChanged(index, index, QVector<int>({role}));
 
            return true;
 
            r = true;
 
        }
 
    } // set visibility
 
    else if (index.column() == COLUMN_VISIBILITY)
 
    {
 
        if (role == Qt::CheckStateRole)
 
        {
 
            bool checked = value.toInt() == Qt::Checked;
 
            info.visibility = checked;
 
            emit dataChanged(index, index, QVector<int>({role}));
 
            return true;
 
            r = true;
 
        }
 
    }
 
    else if (index.column() == COLUMN_GAIN)
 
    {
 
        if (role == Qt::DisplayRole || role == Qt::EditRole)
 
        {
 
            info.gain = value.toDouble();
 
            emit dataChanged(index, index, QVector<int>({role}));
 
            return true;
 
            r = true;
 
        }
 
        else if (role == Qt::CheckStateRole)
 
        {
 
            bool checked = value.toInt() == Qt::Checked;
 
            info.gainEn = checked;
 
            emit dataChanged(index, index, QVector<int>({role}));
 
            return true;
 
            if (_gainOrOffsetEn != checked) updateGainOrOffsetEn();
 
            r = true;
 
        }
 
    }
 
    else if (index.column() == COLUMN_OFFSET)
 
    {
 
        if (role == Qt::DisplayRole || role == Qt::EditRole)
 
        {
 
            info.offset = value.toDouble();
 
            emit dataChanged(index, index, QVector<int>({role}));
 
            return true;
 
            r = true;
 
        }
 
        else if (role == Qt::CheckStateRole)
 
        {
 
            bool checked = value.toInt() == Qt::Checked;
 
            info.offsetEn = checked;
 
            emit dataChanged(index, index, QVector<int>({role}));
 
            return true;
 
            if (_gainOrOffsetEn != checked) updateGainOrOffsetEn();
 
            r = true;
 
        }
 
    }
 

	
 
    // invalid index/role
 
    return false;
 
    if (r)
 
    {
 
        emit dataChanged(index, index, QVector<int>({role}));
 
    }
 

	
 
    return r;
 
}
 

	
 
void ChannelInfoModel::setNumOfChannels(unsigned number)
 
{
 
    if (number == _numOfChannels) return;
 

	
 
    bool isInserting = number > _numOfChannels;
 
    if (isInserting)
 
    {
 
        beginInsertRows(QModelIndex(), _numOfChannels, number-1);
 
    }
 
    else
 
    {
 
        beginRemoveRows(QModelIndex(), number, _numOfChannels-1);
 
    }
 

	
 
    // we create channel info but never remove channel info to
 
    // remember user entered info
 
    if ((int) number > infos.length())
 
    {
 
        for (unsigned ci = infos.length(); ci < number; ci++)
 
        {
 
            infos.append(ChannelInfo(ci));
 
        }
 
    }
 

	
 
    // make sure newly available channels are visible, we don't
 
    // remember visibility option intentionally so that user doesn't
 
    // get confused
 
    if (number > _numOfChannels)
 
    {
 
        for (unsigned ci = _numOfChannels; ci < number; ci++)
 
        {
 
            infos[ci].visibility = true;
 
        }
 
    }
 

	
 
    _numOfChannels = number;
 
    updateGainOrOffsetEn();
 

	
 
    if (isInserting)
 
    {
 
        endInsertRows();
 
    }
 
    else
 
    {
 
        endRemoveRows();
 
    }
 
}
 

	
 
void ChannelInfoModel::resetInfos()
 
{
 
    beginResetModel();
 
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
 
    {
 
        infos[ci] = ChannelInfo(ci);
 
    }
 
    endResetModel();
 
}
 

	
 
// TODO: fix repetitive code, ChannelInfoModel::reset* functions
 
void ChannelInfoModel::resetNames()
 
{
 
    beginResetModel();
 
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
 
    {
 
        // TODO: do not create a full object every time (applies to other reset methods as well)
 
        infos[ci].name = ChannelInfo(ci).name;
 
    }
 
    endResetModel();
 
}
 

	
 
void ChannelInfoModel::resetColors()
 
{
 
    beginResetModel();
 
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
 
    {
 
        infos[ci].color = ChannelInfo(ci).color;
 
    }
 
    endResetModel();
 
}
 

	
 
void ChannelInfoModel::resetVisibility(bool visible)
 
{
 
    beginResetModel();
 
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
 
    {
 
        infos[ci].visibility = visible;
 
    }
 
    endResetModel();
 
}
 

	
 
void ChannelInfoModel::resetGains()
 
{
 
    beginResetModel();
 
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
 
    {
 
        infos[ci].gain = ChannelInfo(ci).gain;
 
        infos[ci].gainEn = ChannelInfo(ci).gainEn;
 
    }
 
    updateGainOrOffsetEn();
 
    endResetModel();
 
}
 

	
 
void ChannelInfoModel::resetOffsets()
 
{
 
    beginResetModel();
 
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
 
    {
 
        infos[ci].offset = ChannelInfo(ci).offset;
 
        infos[ci].offsetEn = ChannelInfo(ci).offsetEn;
 
    }
 
    updateGainOrOffsetEn();
 
    endResetModel();
 
}
 

	
 
bool ChannelInfoModel::gainOrOffsetEn() const
 
{
 
    return _gainOrOffsetEn;
 
}
 

	
 
void ChannelInfoModel::updateGainOrOffsetEn()
 
{
 
    _gainOrOffsetEn = false;
 
    for (int ci = 0; ci < _numOfChannels; ci++)
 
    {
 
        auto& info = infos[ci];
 
        _gainOrOffsetEn |= (info.gainEn || info.offsetEn);
 
    }
 
}
 

	
 
void ChannelInfoModel::saveSettings(QSettings* settings) const
 
{
 
    settings->beginGroup(SettingGroup_Channels);
 
    settings->beginWriteArray(SG_Channels_Channel);
 

	
 
    // save all channel information regardless of current number of channels
 
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
 
    {
 
        settings->setArrayIndex(ci);
 
        auto& info = infos[ci];
 
        settings->setValue(SG_Channels_Name, info.name);
 
        settings->setValue(SG_Channels_Color, info.color);
 
        settings->setValue(SG_Channels_Visible, info.visibility);
 
        settings->setValue(SG_Channels_Gain, info.gain);
 
        settings->setValue(SG_Channels_GainEn, info.gainEn);
 
        settings->setValue(SG_Channels_Offset, info.offset);
 
        settings->setValue(SG_Channels_OffsetEn, info.offsetEn);
 
    }
 

	
 
    settings->endArray();
 
    settings->endGroup();
 
}
 

	
 
void ChannelInfoModel::loadSettings(QSettings* settings)
 
{
 
    settings->beginGroup(SettingGroup_Channels);
 
    unsigned size = settings->beginReadArray(SG_Channels_Channel);
 

	
 
    for (unsigned ci = 0; ci < size; ci++)
 
    {
 
        settings->setArrayIndex(ci);
 

	
 
        ChannelInfo chanInfo(ci);
 
        chanInfo.name       = settings->value(SG_Channels_Name     , chanInfo.name).toString();
 
        chanInfo.color      = settings->value(SG_Channels_Color    , chanInfo.color).value<QColor>();
 
        chanInfo.visibility = settings->value(SG_Channels_Visible  , chanInfo.visibility).toBool();
 
        chanInfo.gain       = settings->value(SG_Channels_Gain     , chanInfo.gain).toDouble();
 
        chanInfo.gainEn     = settings->value(SG_Channels_GainEn   , chanInfo.gainEn).toBool();
 
        chanInfo.offset     = settings->value(SG_Channels_Offset   , chanInfo.offset).toDouble();
 
        chanInfo.offsetEn   = settings->value(SG_Channels_OffsetEn , chanInfo.offsetEn).toBool();
 

	
 
        if ((int) ci < infos.size())
 
        {
 
            infos[ci] = chanInfo;
 

	
 
            if (ci < _numOfChannels)
 
            {
 
                auto roles = QVector<int>({
 
                    Qt::DisplayRole, Qt::EditRole, Qt::ForegroundRole, Qt::CheckStateRole});
 
                emit dataChanged(index(ci, 0), index(ci, COLUMN_COUNT-1), roles);
 
            }
 
        }
 
        else
 
        {
 
            infos.append(chanInfo);
 
        }
 
    }
 

	
 
    updateGainOrOffsetEn();
 

	
 
    settings->endArray();
 
    settings->endGroup();
 
}
src/channelinfomodel.h
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef CHANNELINFOMODEL_H
 
#define CHANNELINFOMODEL_H
 

	
 
#include <QAbstractTableModel>
 
#include <QColor>
 
#include <QSettings>
 
#include <QStringList>
 

	
 
class ChannelInfoModel : public QAbstractTableModel
 
{
 
    Q_OBJECT
 

	
 
public:
 
    enum ChannelInfoColumn
 
    {
 
        COLUMN_NAME = 0,
 
        COLUMN_VISIBILITY,
 
        COLUMN_GAIN,
 
        COLUMN_OFFSET,
 
        COLUMN_COUNT            // MUST be last
 
    };
 

	
 
    explicit ChannelInfoModel(unsigned numberOfChannels, QObject *parent = 0);
 
    ChannelInfoModel(const ChannelInfoModel& other);
 
    explicit ChannelInfoModel(const QStringList& channelNames);
 

	
 
    QString name     (unsigned i) const;
 
    QColor  color    (unsigned i) const;
 
    bool    isVisible(unsigned i) const;
 
    bool    gainEn   (unsigned i) const;
 
    double  gain     (unsigned i) const;
 
    bool    offsetEn (unsigned i) const;
 
    double  offset   (unsigned i) const;
 
    /// Returns true if any of the channels have gain or offset enabled
 
    bool gainOrOffsetEn() const;
 
    /// Returns a list of channel names
 
    QStringList channelNames() const;
 

	
 
    // implemented from QAbstractItemModel
 
    int           rowCount(const QModelIndex &parent = QModelIndex()) const;
 
    int           columnCount(const QModelIndex &parent = QModelIndex()) const;
 
    QVariant      data(const QModelIndex &index, int role = Qt::DisplayRole) const;
 
    bool          setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
 
    Qt::ItemFlags flags(const QModelIndex &index) const;
 
    QVariant      headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
 

	
 
    void setNumOfChannels(unsigned number);
 
    /// Stores all channel info into a `QSettings`
 
    void saveSettings(QSettings* settings) const;
 
    /// Loads all channel info from a `QSettings`.
 
    void loadSettings(QSettings* settings);
 

	
 
public slots:
 
    /// reset all channel info (names, color etc.)
 
    void resetInfos();
 
    /// reset all channel names
 
    void resetNames();
 
    /// reset all channel colors
 
    void resetColors();
 
    /// reset all channel gain values and disables gains
 
    void resetGains();
 
    /// reset all channel offset values and disables offsets
 
    void resetOffsets();
 
    /// reset visibility
 
    void resetVisibility(bool visible);
 

	
 
private:
 
    struct ChannelInfo
 
    {
 
        explicit ChannelInfo(unsigned index);
 

	
 
        QString name;
 
        bool visibility;
 
        QColor color;
 
        double gain, offset;
 
        bool gainEn, offsetEn;
 
    };
 

	
 
    unsigned _numOfChannels;     ///< @note this is not necessarily the length of `infos`
 

	
 
    /**
 
     * Channel info is added here but never removed so that we can
 
     * remember user entered info (names, colors etc.).
 
     */
 
    QList<ChannelInfo> infos;
 

	
 
    /**
 
     * Cache for gain and offset enabled variables of channels. If gain and/or
 
     * offset is not enabled for *any* of the channels this is false otherwise
 
     * true.
 
     */
 
    bool _gainOrOffsetEn;
 

	
 
    /// Updates `_gainOrOffsetEn` by scanning all channel infos.
 
    void updateGainOrOffsetEn();
 
};
 

	
 
#endif // CHANNELINFOMODEL_H
src/stream.cpp
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "stream.h"
 
#include "ringbuffer.h"
 
#include "indexbuffer.h"
 

	
 
Stream::Stream(unsigned nc, bool x, unsigned ns) :
 
    _infoModel(nc)
 
{
 
    _numSamples = ns;
 
    _paused = false;
 

	
 
    // create xdata buffer
 
    _hasx = x;
 
    if (x)
 
    {
 
        xData = new RingBuffer(ns);
 
    }
 
    else
 
    {
 
        xData = new IndexBuffer(ns);
 
    }
 

	
 
    // create channels
 
    for (unsigned i = 0; i < nc; i++)
 
    {
 
        auto c = new StreamChannel(i, xData, new RingBuffer(ns), &_infoModel);
 
        channels.append(c);
 
    }
 
}
 

	
 
Stream::~Stream()
 
{
 
    for (auto ch : channels)
 
    {
 
        delete ch;
 
    }
 
    delete xData;
 
}
 

	
 
bool Stream::hasX() const
 
{
 
    return _hasx;
 
}
 

	
 
unsigned Stream::numChannels() const
 
{
 
    return channels.length();
 
}
 

	
 
unsigned Stream::numSamples() const
 
{
 
    return _numSamples;
 
}
 

	
 
const StreamChannel* Stream::channel(unsigned index) const
 
{
 
    Q_ASSERT(index < numChannels());
 
    return channels[index];
 
}
 

	
 
StreamChannel* Stream::channel(unsigned index)
 
{
 
    return const_cast<StreamChannel*>(static_cast<const Stream&>(*this).channel(index));
 
}
 

	
 
const ChannelInfoModel* Stream::infoModel() const
 
{
 
    return &_infoModel;
 
}
 

	
 
ChannelInfoModel* Stream::infoModel()
 
{
 
    return const_cast<ChannelInfoModel*>(static_cast<const Stream&>(*this).infoModel());
 
}
 

	
 
void Stream::setNumChannels(unsigned nc, bool x)
 
{
 
    unsigned oldNum = numChannels();
 
    if (oldNum == nc && x == _hasx) return;
 

	
 
    // adjust the number of channels
 
    if (nc > oldNum)
 
    {
 
        for (unsigned i = oldNum; i < nc; i++)
 
        {
 
            auto c = new StreamChannel(i, xData, new RingBuffer(_numSamples), &_infoModel);
 
            channels.append(c);
 
        }
 
    }
 
    else if (nc < oldNum)
 
    {
 
        for (unsigned i = oldNum-1; i > nc-1; i--)
 
        {
 
            delete channels.takeLast();
 
        }
 
    }
 

	
 
    // change the xdata
 
    if (x != _hasx)
 
    {
 
        if (x)
 
        {
 
            xData = new RingBuffer(_numSamples);
 
        }
 
        else
 
        {
 
            xData = new IndexBuffer(_numSamples);
 
        }
 

	
 
        for (auto c : channels)
 
        {
 
            c->setX(xData);
 
        }
 
        _hasx = x;
 
    }
 

	
 
    if (nc != oldNum)
 
    {
 
        _infoModel.setNumOfChannels(nc);
 
        // TODO: how about X change?
 
        emit numChannelsChanged(nc);
 
    }
 

	
 
    Sink::setNumChannels(nc, x);
 
}
 

	
 
const SamplePack* Stream::applyGainOffset(const SamplePack& pack) const
 
{
 
    SamplePack* mPack = nullptr;
 
    Q_ASSERT(infoModel()->gainOrOffsetEn());
 

	
 
    SamplePack* mPack = new SamplePack(pack);
 
    unsigned ns = pack.numSamples();
 

	
 
    for (unsigned ci = 0; ci < numChannels(); ci++)
 
    {
 
        bool gainEn = channels[ci]->info()->gainEn(ci);
 
        bool offsetEn = channels[ci]->info()->offsetEn(ci);
 
        double* mdata;
 
        // TODO: we could use some kind of map (int32, int64 would suffice) to speed things up
 
        bool gainEn = infoModel()->gainEn(ci);
 
        bool offsetEn = infoModel()->offsetEn(ci);
 
        if (gainEn || offsetEn)
 
        {
 
            if (mPack == nullptr)
 
                mPack = new SamplePack(pack);
 
            double* mdata = mPack->data(ci);
 

	
 
            mdata = mPack->data(ci);
 

	
 
            double gain = channels[ci]->info()->gain(ci);
 
            double offset = channels[ci]->info()->offset(ci);
 
            double gain = infoModel()->gain(ci);
 
            double offset = infoModel()->offset(ci);
 

	
 
            if (gainEn)
 
            {
 
                for (unsigned i = 0; i < ns; i++)
 
                {
 
                    mdata[i] *= gain;
 
                }
 
            }
 
            if (offsetEn)
 
            {
 
                for (unsigned i = 0; i < ns; i++)
 
                {
 
                    mdata[i] += offset;
 
                }
 
            }
 
        }
 
    }
 

	
 
    return mPack;
 
}
 

	
 
void Stream::feedIn(const SamplePack& pack)
 
{
 
    Q_ASSERT(pack.numChannels() == numChannels() &&
 
             pack.hasX() == hasX());
 

	
 
    if (_paused) return;
 

	
 
    unsigned ns = pack.numSamples();
 
    if (_hasx)
 
    {
 
        static_cast<RingBuffer*>(xData)->addSamples(pack.xData(), ns);
 
    }
 

	
 
    // modified pack that gain and offset is applied to
 
    const SamplePack* mPack = applyGainOffset(pack);
 
    const SamplePack* mPack = nullptr;
 
    if (infoModel()->gainOrOffsetEn())
 
        mPack = applyGainOffset(pack);
 

	
 
    for (unsigned ci = 0; ci < numChannels(); ci++)
 
    {
 
        auto buf = static_cast<RingBuffer*>(channels[ci]->yData());
 
        double* data = (mPack == nullptr) ? pack.data(ci) : mPack->data(ci);
 
        buf->addSamples(data, ns);
 
    }
 

	
 
    Sink::feedIn((mPack == nullptr) ? pack : *mPack);
 

	
 
    if (mPack != nullptr) delete mPack;
 
    emit dataAdded();
 
}
 

	
 
void Stream::pause(bool paused)
 
{
 
    _paused = paused;
 
}
 

	
 
void Stream::clear()
 
{
 
    for (auto c : channels)
 
    {
 
        static_cast<RingBuffer*>(c->yData())->clear();
 
    }
 
}
 

	
 
void Stream::setNumSamples(unsigned value)
 
{
 
    if (value == _numSamples) return;
 
    _numSamples = value;
 

	
 
    xData->resize(value);
 
    for (auto c : channels)
 
    {
 
        static_cast<RingBuffer*>(c->yData())->resize(value);
 
    }
 
}
 

	
 
void Stream::saveSettings(QSettings* settings) const
 
{
 
    _infoModel.saveSettings(settings);
 
}
 

	
 
void Stream::loadSettings(QSettings* settings)
 
{
 
    _infoModel.loadSettings(settings);
 
}
src/stream.h
Show inline comments
 
/*
 
  Copyright © 2018 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef STREAM_H
 
#define STREAM_H
 

	
 
#include <QObject>
 
#include <QModelIndex>
 
#include <QVector>
 
#include <QSettings>
 

	
 
#include "sink.h"
 
#include "source.h"
 
#include "channelinfomodel.h"
 
#include "streamchannel.h"
 
#include "framebuffer.h"
 

	
 
/**
 
 * Main waveform storage class. It consists of channels. Channels are
 
 * synchronized with each other.
 
 *
 
 * Implements `Sink` class for data entry. It's expected to be
 
 * connected to a `Device` source.
 
 */
 
class Stream : public QObject, public Sink
 
{
 
    Q_OBJECT
 

	
 
public:
 
    /**
 
     * @param nc number of channels
 
     * @param x has X data input
 
     * @param ns number of samples
 
     */
 
    Stream(unsigned nc = 0, bool x = false, unsigned ns = 0);
 
    ~Stream();
 

	
 
    // implementations for `Source`
 
    virtual bool hasX() const;
 
    virtual unsigned numChannels() const;
 

	
 
    unsigned numSamples() const;
 
    const StreamChannel* channel(unsigned index) const;
 
    StreamChannel* channel(unsigned index);
 
    const ChannelInfoModel* infoModel() const;
 
    ChannelInfoModel* infoModel();
 

	
 
    /// Saves channel information
 
    void saveSettings(QSettings* settings) const;
 
    /// Load channel information
 
    void loadSettings(QSettings* settings);
 

	
 
protected:
 
    // implementations for `Sink`
 
    virtual void setNumChannels(unsigned nc, bool x);
 
    virtual void feedIn(const SamplePack& pack);
 

	
 
signals:
 
    void numChannelsChanged(unsigned value);
 
    void numSamplesChanged(unsigned value);
 
    void channelAdded(const StreamChannel* chan);
 
    void channelNameChanged(unsigned channel, QString name); // TODO: does it stay?
 
    void dataAdded(); ///< emitted when data added to channel man.
 

	
 
public slots:
 
    // TODO: these won't be public
 
    // void setNumChannels(unsigned number);
 
    void setNumSamples(unsigned value);
 

	
 
    /// When paused data feed is ignored
 
    void pause(bool paused);
 

	
 
    /// Clears buffer data (fills with 0)
 
    void clear();
 

	
 
private:
 
    unsigned _numSamples;
 
    bool _paused;
 

	
 
    bool _hasx;
 
    ResizableBuffer* xData;
 
    QList<StreamChannel*> channels;
 

	
 
    ChannelInfoModel _infoModel;
 

	
 
    /**
 
     * Applies gain and offset to given pack.
 
     *
 
     * If gain or offset isn't enabled this function returns `nullptr`. If gain
 
     * or offset is enabled for at least 1 channel input pack is copied and
 
     * modified. Result is returned. Caller is responsible for deleting
 
     * returned `SamplePack`.
 
     * Caller is responsible for deleting returned `SamplePack`.
 
     *
 
     * @note Should be called only when gain or offset is enabled. Guard with
 
     * `ChannelInfoModel::gainOrOffsetEn()`.
 
     *
 
     * @param pack input data
 
     * @return `nullptr` if no gain or offset is enabled otherwise modified data
 
     * @return modified data
 
     */
 
    const SamplePack* applyGainOffset(const SamplePack& pack) const;
 
};
 

	
 

	
 
#endif // STREAM_H
0 comments (0 inline, 0 general)