diff --git a/src/stream.cpp b/src/stream.cpp
new file mode 100644
--- /dev/null
+++ b/src/stream.cpp
@@ -0,0 +1,200 @@
+/*
+  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 .
+*/
+
+#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(static_cast(*this).channel(index));
+}
+
+const ChannelInfoModel* Stream::infoModel() const
+{
+    return &_infoModel;
+}
+
+ChannelInfoModel* Stream::infoModel()
+{
+    return const_cast(static_cast(*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);
+}
+
+void Stream::feedIn(const SamplePack& data)
+{
+    Q_ASSERT(data.numChannels() == numChannels() &&
+             data.hasX() == hasX());
+
+    if (_paused) return;
+
+    unsigned ns = data.numSamples();
+    if (_hasx)
+    {
+        static_cast(xData)->addSamples(data.xData(), ns);
+    }
+    for (unsigned i = 0; i < numChannels(); i++)
+    {
+        static_cast(channels[i]->yData())->addSamples(data.data(i), ns);
+    }
+
+    Sink::feedIn(data);
+
+    emit dataAdded();
+}
+
+void Stream::pause(bool paused)
+{
+    _paused = paused;
+}
+
+void Stream::clear()
+{
+    for (auto c : channels)
+    {
+        static_cast(c->yData())->clear();
+    }
+}
+
+void Stream::setNumSamples(unsigned value)
+{
+    if (value == _numSamples) return;
+    _numSamples = value;
+
+    xData->resize(value);
+    for (auto c : channels)
+    {
+        static_cast(c->yData())->resize(value);
+    }
+}
+
+void Stream::saveSettings(QSettings* settings) const
+{
+    _infoModel.saveSettings(settings);
+}
+
+void Stream::loadSettings(QSettings* settings)
+{
+    _infoModel.loadSettings(settings);
+}