QByteArray including floats

668 Views Asked by At

I am a newbie of C++ and QT and i am actually on a project of writing a sound signal generator. But my problem is,

I am creating the floats to be implemented in qbytearray which i will use to fill qbuffer. But i can not get float into the qbytearray, it gives a warning saying "losing precision". And qbytearray consists of only integer values from -100 to 100. i need the floats with desired precision.

Can you help me ?

void MainWindow::toneGenerate(){

    int len= m_seconds*SAMPLE_RATE;

    sinbuf.resize(len);

    for(int i=0;i<len;i++){
        qreal t = m_freq*i;
        t *= FREQ_CONST;
        t = t+ m_ph;
        t = qSin(t);
        t*= m_amp;

        sinbuf[i] = t;
    }

    sininput.setBuffer(&sinbuf);
    sininput.open(QIODevice::ReadWrite);
}
2

There are 2 best solutions below

7
Antonio Dias On

When writing code for sound development, is important take care about the size of each sample, the byte order for storing the samples as binary data, and if is necessary to write a header to the data, or if it's raw, header-less.

And if your goal is fill a QBuffer you can write to it trough QDataStream, and read it back if you will.

In my answer, I'll assume Little Endian, and instead of float, I'll use 16 bits signed integer samples, 1 channel and 8000Hz frequency.

I'm providing a simple example of tone generator, please adapt it to your needs!

Let's see the following console example:

#include <QtCore>
#include <QtMultimedia>

static QBuffer m_float_buffer;

void toneGenerator()
{
    QDataStream write_stream(&m_float_buffer);
    write_stream.setVersion(QDataStream::Qt_5_0); //Protocol for version 5.0
    write_stream.setByteOrder(QDataStream::LittleEndian);

    //Tone generator from http://www.cplusplus.com/forum/general/129827/

    const unsigned int samplerate = 8000;
    const unsigned short channels = 1;

    const double pi = M_PI;
    const qint16 amplitude = qint16(INT16_MAX * 0.5);

    const unsigned short n_frequencies = 8;
    const unsigned short n_seconds_each = 1;

    float frequencies[n_frequencies] = {55.0, 110.0, 220.0, 440.0, 880.0, 1760.0, 3520.0, 7040.0};

    const int n_samples = channels * samplerate * n_frequencies * n_seconds_each;

    int index = n_samples / n_frequencies;

    for (unsigned short i = 0; i < n_frequencies; i++)
    {
        float freq = frequencies[i];
        float d = (samplerate / freq);
        int c = 0;

        for (int j = index * i; j < index * (i + 1); j++)
        {
            float deg = 360.0f / d;
            write_stream << qint16(qSin((c++ * double(deg)) * pi / 180.0) * amplitude);
        }
    }
}

void dataPlay()
{
    QAudioFormat format;
    format.setCodec("audio/pcm");
    format.setSampleRate(8000);
    format.setChannelCount(1);
    format.setSampleSize(16);
    format.setByteOrder(QAudioFormat::LittleEndian);
    format.setSampleType(QAudioFormat::SignedInt);

    QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());

    if (!info.isFormatSupported(format))
    {
        qDebug() << "Raw audio format not supported by backend, cannot play audio.";
        return;
    }

    QAudioOutput audio(format);

    QEventLoop loop;

    QObject::connect(&audio, &QAudioOutput::stateChanged, &audio, [&](const QAudio::State state){
        if (state != QAudio::ActiveState)
            loop.quit();
    });

    audio.start(&m_float_buffer);

    loop.exec();
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "Opening buffer...";

    m_float_buffer.open(QBuffer::ReadWrite);

    qDebug() << "\nGenerating...";
    toneGenerator();

    //Back to beginning, now for reading
    m_float_buffer.seek(0);

    qDebug() << "\nPlaying...";
    dataPlay();

    qDebug() << "\nQBuffer size:" << m_float_buffer.size() << "bytes";

    return a.exec();
}
0
mrg95 On

The [] operator on a QByteArray only references a single byte (8 bits long) however a float is 4 bytes (32 bits long).

Instead of sinbuf[i] = t; which will only store the first 8 bits of the float, you should store the entire float which will store all 32 bits.

This template function will return a QByteArray that you can append to sinbuf

template<typename T>
static QByteArray numToByteArray(T num, bool isLE = false)
{
    QByteArray ba("");
    if(isLE){
        ba.resize(sizeof(T));
        memcpy(ba.data(), &num, sizeof(T));
    }
    else{
        for(int i=sizeof(T)-1; i>=0; i--)
            ba.append(quint8(num>>(i*8)));
    }

    return ba;
}

Usage:

void MainWindow::toneGenerate(){

    int len= m_seconds*SAMPLE_RATE;

    //sinbuf.resize(len); calls to append will resize for you

    for(int i=0;i<len;i++){
        qreal t = m_freq*i;
        t *= FREQ_CONST;
        t = t+ m_ph;
        t = qSin(t);
        t*= m_amp;

        //You will have to account for endianness
        //Pass true as a second argument here if it's Little Endian
        sinbuf.append(numToByteArray<float>(t));
    }

    sininput.setBuffer(&sinbuf);
    sininput.open(QIODevice::ReadWrite);

    //You will want to write directly to the device stream
    //because sinbuf will store everything in memory
}

Of course this is entirely dependent on the sample size and endianness...