0

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);
}
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • If `sinbuf` is a `QByteArray` object (hard to tell without a [mcve]), then `&sinbuf` is a pointer to the `QByteArray` object, not to the data it contains. – Some programmer dude Mar 19 '19 at 16:49
  • `operator[]` for `QByteArray` returns `char&`, so you are storing only one byte from `qreal` (if qreal is double, 7 bytes from double are not stored) in `sinbuf[i] = t`. You need to add all bytes from qreal into QByteArray - read [this topic](https://stackoverflow.com/questions/26841755/how-to-convert-qvectordouble-to-qbytearray). Store qreal in QVector then convert it into QByteArray. – rafix07 Mar 19 '19 at 16:59
  • thank you. will try to make a qvector and convert it into qbytearray – Bora Çelebi Mar 19 '19 at 17:16
  • tried it. qvector has been populated truly. but when i try to write it to the qbytearray it just puts the floats randomly. tried all the methods in that post @rafix07 has provided. – Bora Çelebi Mar 19 '19 at 21:26

2 Answers2

0

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();
}
Antonio Dias
  • 2,751
  • 20
  • 40
  • thank you so much for your response. I did it but now audiooutput just gives dirty noise. Can it be a problem of sample size? – Bora Çelebi Mar 20 '19 at 10:09
  • int len= m_seconds*SAMPLE_RATE; QDataStream write_stream (&sininput); write_stream.setVersion(QDataStream::Qt_5_0); write_stream.setByteOrder(QDataStream::LittleEndian); write_stream.setFloatingPointPrecision(QDataStream::SinglePrecision); sininput.open(QIODevice::WriteOnly); for(int i=0;i – Bora Çelebi Mar 20 '19 at 10:11
  • sininput.open(QIODevice::ReadOnly); sininput.seek(0); audio = new QAudioOutput(format,this); audio->start(&sininput); – Bora Çelebi Mar 20 '19 at 10:11
  • actually it gives 4 different frequencies at the same time – Bora Çelebi Mar 20 '19 at 10:29
  • Actually now it generates a square audio. When i plot it. İt s a fine sinus wave data but when i output stream it becomes a square. How can this happen? – Bora Çelebi Mar 20 '19 at 18:33
  • Please see my edit! Also note that i changed from float to integer because Qt multimedia has some problems with playing float samples. – Antonio Dias Mar 21 '19 at 00:38
  • thank you so much. my wave is now sine again. but the problem is i managed it to do in int type.. i need floats for better precision sound is a little bit dirty now. – Bora Çelebi Mar 21 '19 at 17:24
0

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...

mrg95
  • 2,371
  • 11
  • 46
  • 89