HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

ASCII graphic waveform generator

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
waveformgraphicasciigenerator

Problem

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 
#include 
#include 
#include 

class waveform
{
public:
    waveform(std::string &s) : data_(s) {}
    std::vector to_bits() const
    {
        std::vector bits;
        bits.reserve(data_.size() * 10);
        for (auto &ch : data_) {
            bits.push_back(0); // start bit
            for (int mask=1u; mask != 0x100; mask >= 1;
            if (special == 0)
                special = 0x201;
        }
        out << '\n';
        return out;
    }
private:
    std::string data_;
};


Sample driver

int main()
{
    std::string msg{"ST"};
    std::cout << waveform(msg) << 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.

Solution

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<< 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 then changed your mind), you could do better by defining a filtering output stream. Here's my attempt to implement such a thing:

#include 
#include 
#include 
#include 
#include 

class waveform_ostream : public std::streambuf
{
public:
    explicit waveform_ostream(std::ostream &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 > i) & 1);   // Data bits, LSB first
        }
        output_bit(1, 'P');             // Stop bit
        return c;
    }

    // Flush
    int sync() {
        for (int row = 0; row str();
            sink str("");
        }
        sink << std::flush;
        return 0;   // success
    }

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

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

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

Code Snippets

#include <climits>
#include <iostream>
#include <sstream>
#include <streambuf>
#include <string>

class waveform_ostream : public std::streambuf
{
public:
    explicit waveform_ostream(std::ostream &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 < CHAR_BIT; ++i) {
            output_bit((c >> i) & 1);   // Data bits, LSB first
        }
        output_bit(1, 'P');             // Stop bit
        return c;
    }

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

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

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

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

Context

StackExchange Code Review Q#88148, answer score: 4

Revisions (0)

No revisions yet.