ASCII graphic waveform generator Working on some documentation lately, I had a need to convert a number of asynchronous serial waveforms into ASCII graphics characters.  Rather than draw them by hand, I decided to write a program to do it for me.  I'm interested in a code review, and particularly if there are smarter/shorter ways of coding the class.

#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;string&gt;
#include &lt;bitset&gt;

class waveform
{
public:
    waveform(std::string &amp;s) : data_(s) {}
    std::vector&lt;bool&gt; to_bits() const
    {
        std::vector&lt;bool&gt; bits;
        bits.reserve(data_.size() * 10);
        for (auto &amp;ch : data_) {
            bits.push_back(0); // start bit
            for (int mask=1u; mask != 0x100; mask &lt;&lt;= 1) {
                bits.push_back(ch &amp; mask);
            }
            bits.push_back(1); // stop bit
        }
        return bits;
    }
    friend std::ostream&amp; operator&lt;&lt;(std::ostream&amp; out, const waveform&amp; w) {
        auto bits = w.to_bits();
        bool last = 1;
        for (const auto &amp;b : bits) {
            out &lt;&lt; (b ? "+---" : (last ? "+   " : "    "));
            last = b;
        }
        out &lt;&lt; '\n';
        last = 1;
        for (const auto &amp;b : bits) {
            out &lt;&lt; (b != last ? "|   " : "    ");
            last = b;
        }
        out &lt;&lt; '\n';
        last = 1;
        for (const auto &amp;b : bits) {
            out &lt;&lt; (b ? (last ? "    " : "+   ") : "+---");
            last = b;
        }
        out &lt;&lt; '\n';
        int special = 0x201;
        for (const auto &amp;b : bits) {
            if (special &amp; 1) {
                out &lt;&lt; (b ? "  P " : "  S ");
            } else {
                out &lt;&lt; (b ? "  1 " : "  0 ");
            }
            special &gt;&gt;= 1;
            if (special == 0)
                special = 0x201;
        }
        out &lt;&lt; '\n';
        return out;
    }
private:
    std::string data_;
};


Sample driver

int main()
{
    std::string msg{"ST"};
    std::cout &lt;&lt; waveform(msg) &lt;&lt; std::endl;
}


Sample output

+   +---+---+       +---+   +---+   +---+           +---+   +---+   +---+   +---
|   |       |       |   |   |   |   |   |           |   |   |   |   |   |   |   
+---+       +---+---+   +---+   +---+   +---+---+---+   +---+   +---+   +---+   
  S   1   1   0   0   1   0   1   0   P   S   0   0   1   0   1   0   1   0   P 


Note that in this diagram, S stands for a start bit, P stands for a stop bit and the 8 data bits are sent least significant bit first.
The OOP design doesn't seem to be doing much for you.  It feels like you could get basically the same effect using just a function.  On the other hand, the operator&lt;&lt; method is basically a state machine, and it looks somewhat nastier than it needs to be.  There is copy-and-paste code, with bits of ASCII art all over the place, and a mini-Turing machine using the magic number 0x201.

Assuming that the to_bits() helper function isn't important to you (I assume it isn't that important, because you wrote #include &lt;bitset&gt; then changed your mind), you could do better by defining a filtering output stream.  Here's my attempt to implement such a thing:

#include &lt;climits&gt;
#include &lt;iostream&gt;
#include &lt;sstream&gt;
#include &lt;streambuf&gt;
#include &lt;string&gt;

class waveform_ostream : public std::streambuf
{
public:
    explicit waveform_ostream(std::ostream &amp;sink) : sink(sink) {}

protected:
    // Accept a byte of data
    virtual std::streambuf::int_type overflow(std::streambuf::int_type c) {
        output_bit(0, 'S');             // Start bit
        for (int i = 0; i &lt; CHAR_BIT; ++i) {
            output_bit((c &gt;&gt; i) &amp; 1);   // Data bits, LSB first
        }
        output_bit(1, 'P');             // Stop bit
        return c;
    }

    // Flush
    int sync() {
        for (int row = 0; row &lt; 4; ++row) {
            std::string s = lines[row]-&gt;str();
            sink &lt;&lt; s &lt;&lt; '\n';
            lines[row]-&gt;str("");
        }
        sink &lt;&lt; std::flush;
        return 0;   // success
    }

private:
    bool state = 1;
    std::ostream &amp;sink;
    std::stringstream level[2], risefall, captions;
    std::stringstream *const lines[4] = {
        &amp;level[1],
        &amp;risefall,
        &amp;level[0],
        &amp;captions
    };

    void output_bit(bool b, char caption='\0') {
        level[!b] &lt;&lt; ((b == state) ? "    " : "+   ");
        risefall  &lt;&lt; ((b == state) ? "    " : "|   ");
        level[ b] &lt;&lt;                          "+---";
        captions  &lt;&lt; "  " &lt;&lt; (char)(caption ? caption : b ? '1' : '0') &lt;&lt; ' ';
        state = b;
    }
};

int main()
{
    waveform_ostream wout(std::cout);
    std::ostream out(&amp;wout);
    out &lt;&lt; std::string("ST") &lt;&lt; std::flush;
}