CS 312 - Week 8.1 Class
CS 312 Audio Programming Winter 2020

Table of Contents

8.1 Class

In this assignment you're going to learn how to Open, Save, Play, and display information in wav files using libsndfile and Qt.

Homework 8.1

hw811_qtwavio

Setup
cd $HOME312/cs312/
mkdir hw81
Download wav files used in hw81

hw81_wav.zip

Download (right click) this file into your hw81 folder.
Double click to unzip.

hw81203.png

You should see these four files.
Copy the hw81_wav folder into hw81.

Create a new Qt Widgets Application

hw81101.png

Name it hw811_qtwavio and save it in the ~/cs312/hw81from83 folder.

hw81102.png

Continue with
  • Define Build System: qmake
  • Class Information: use default
  • Translation File: <none>
  • Kit Selection: Desktop Qt 5.12.4 clang 64bit
  • Project Management: <None>
Create the build-debug folder.
cd $HOME312/cs312/hw81/hw811_qtwavio
mkdir build-debug

Set the Build Directory in Projects view.

hw81109.png

Create this mainwindow.ui layout in Designer view

hw811_layout.png

Add a File menu.

  • Double click Type-Here
  • Type File followed by Return

    hw811_typeHere.png

Add two menu items.

  • Type Open followed by Return
  • Type Save As… followed by Return.
    The dot-dot-dot after a menu item indicates that item opens another window.

    hw81104.png

The variable names I used are shown below.

hw81105.png

Name the Play button and the labels for SF_INFO and Stats.

hw811_varNames.png

Widget action slots

pushButton_Play
Right click the Play button choose Go to slot and choose the triggered(bool) action.

hw81106.png

The two menu items
Slots for the Menu items are created in the Action Editor panel.

hw81107.png

Right click the actionOpen text and choose Go to slot.
Right click the actionSave_As text and choose Go to slot.

hw81108.png

mainwindow.h
You should see three private slots appear in mainwindow.h.
Add the doMessageBox(…) function to the private section of the MainWindow class.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui
{
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

  public:
    MainWindow( QWidget* parent = nullptr );
    ~MainWindow();

  private slots:
    void on_pushButton_Play_clicked();

    void on_actionOpen_triggered();

    void on_actionSave_As_triggered();

  private:
    Ui::MainWindow* ui;

    void doMessageBox( const QString& qs );
};
#endif // MAINWINDOW_H

mainwindow.cpp
Copy/replace/paste.

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QFileDialog>
#include <QMessageBox>

MainWindow::MainWindow( QWidget* parent )
    : QMainWindow( parent ), ui( new Ui::MainWindow )
{
    ui->setupUi( this );
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_actionOpen_triggered()
{
    /*
       * Call QFileDialog::getOpenFileName
       * Set qCurrentOpenFileName
       * Set label_filename to the name of the file opened
       */

    QString qs = "See \nhttps://doc.qt.io/qt-5/qfiledialog.html#getOpenFileName  \
                          \n\n Then continue to follow steps class web page.";
    doMessageBox( qs );
}

void MainWindow::on_actionSave_As_triggered()
{
    /*
   * Call QFileDialog::getSaveFileName
   * Copy writeWav() from hw811
   * Set qCurrentSaveAsFileName
   */

    QString qs = "See \nhttps://doc.qt.io/qt-5/qfiledialog.html#getSaveFileName  \
            \n\n Then continue to follow steps on class web page.";
    doMessageBox( qs );
}

void MainWindow::on_pushButton_Play_clicked()
{
    doMessageBox( "See \nhttps://doc.qt.io/qt-5/audiooverview.html" );
}

void MainWindow::doMessageBox( const QString& qs )
{
    // copied directly from the Help page for MessageBox
    QMessageBox msgBox;
    msgBox.setText( qs );
    msgBox.exec();
}

Build and Run
I've written code for the three action slots to display a Message Box containing a hint for their implementation.

Test the three Slots
  • Choose Open from the File menu
  • Choose Save As from the File menu
  • Click the Play button
Create wavio.h

This file contains routines to read and write wav files and retrieve information about the type of wav file.

Choose New File or Project from the File menu and create a new C++ Header File.

hw81110.png

Name it wavio.h.
The Path should already show the hw811_qtwavio folder. If not choose it.
Click Continue.

hw81111a.png

On the Project Management page choose your version control and click Done.

hw81111b.png

Qt adds the wavio.h to the project.
You should see the wavio.h file open ready to add code.

hw81112.png

Copy/paste/replace.

#ifndef WAVIO_H
#define WAVIO_H

#ifndef SNDFILE_HH
#include "sndfile.hh"
#endif

#include <string>
#include <vector>

#include <QMessageBox>
#include <QString>

typedef double MY_TYPE;
#define FORMAT RTAUDIO_FLOAT64

std::vector<double> vsamps;

extern void doMessageBox( const QString& qs );
extern std::vector<MY_TYPE> readWav( std::string fname );
extern void writeWav( std::string fname, const std::vector<MY_TYPE>& vin );

#endif // WAVIO_H
Create wavio.cpp

Create the wavio.cpp file following similar steps.

Copy/paste/replace.

#ifndef WAVIO_H
#include "wavio.h"
#endif

std::vector<double> vsamps;

void doMessageBox( const QString& qs )
{
    QMessageBox msgBox;
    msgBox.setText( qs );
    msgBox.exec();
}

std::vector<MY_TYPE> readWav( std::string path )
{
    // Create SndfileHandle class variable named infile initialized with the parameter path

    // get the number of samples in the file and create a vector of that size
    std::vector<double> v( /*number of samples in the file*/ );

    // read samples into v and report the number read in nRead
    // a valid read would report frames == nRead
    sf_count_t nRead = infile.read( &v[ 0 ], infile.frames() );

    // Display the results in the Application Output panel
    // finish each fo the following lines with the appropriate SndfileHandle
    //  function to display that particular information

    // qDebug() << path.c_str();
    // qDebug() << "frames " << ;
    // qDebug() << "format " << ;
    // qDebug() << "channels " << ;
    // qDebug() << "samplerate " << );
    // qDebug() << "nRead " << ;

    return v;
}

void writeWav( std::string fname, const std::vector<MY_TYPE>& v )
{
    doMessageBox( "You need to implement writeWav(...) in wavio.cpp" );

    // from libsndfile example sndfile.hh
    /*
      These are the parameters you'll need for creating a SndfileHandle
      use the constructor shown in the web page notes parameters follow
        path = fname
        mode = SFM_WRITE
        format = SF_FORMAT_WAV | SF_FORMAT_DOUBLE
        channels = 1
        samplerate = 44100

      declare and initialize variable: nWritten
      create the SndfileHandle outfile = SndfileHandle( fname, mode, format, channels, samplerate );
      declare and initialize variable: sz is equal to size of vsamps vector set in readSF()
      call nWritten = outfile.write( &vsamps[ 0 ], sz );
    */
}
Add wavio.h and wavio.cpp to the project

Add the changes in red to the hw811_qtwavio.pro file.

hw811_qtwavio.pro.pdf

Changes to mainwindow.h

Make the changes shown in red.

mainwindow.h.pdf

The following tasks are found in mainwindow.cpp

Task 1: on_actionOpen_triggered()

Consult https://doc.qt.io/qt-5/qfiledialog.html#getOpenFileName

  • Call QFileDialog::getOpenFileName() to retrieve and set the qOpenFileName variable.
  • Set label_filename to the name of the file opened

When done correctly the OpenFile dialog will open to the hw81_wav directory and display all files with a .wav extension.

hw81113.png

The full pathname of the file will be displayed to the right of the Play button.
Hint: You may need to expand the label_filename in the GUI designer.

hw81114.png

Delete these lines from on_actionOpen_triggered()

QString qs = "See \nhttps://doc.qt.io/qt-5/qfiledialog.html#getOpenFileName  \
                  \n\n Then continue to follow steps class web page.";
doMessageBox( qs );
Task 2: on_actionSave_As_triggered()

Consult https://doc.qt.io/qt-5/qfiledialog.html#getOpenFileName

  • Call QFileDialog::getSaveFileName() to retrieve and set the /qSaveAsFileName variable.

When done correctly the SaveFile dialog will open to the hw811_qtwavio directory.

hw81115a.png

  • Name the file and click the Save button.
  • Use the doMessage(…) function to display the Save As filename.

    hw81115b.png

Delete these lines from on_actionSave_As_triggered()

QString qs = "See \nhttps://doc.qt.io/qt-5/qfiledialog.html#getSaveFileName  \
    \n\n Then continue to follow steps on class web page.";
doMessageBox( qs );
Task 3: Open a wav file, display info about the file, and return a vector of samples

Move doMessageBox() to wavio.cpp

  • Remove doMessageBox from mainwindow.h
  • Remove #include <QMessageBox> from mainwindow.cpp
  • Cut doMessageBox from mainwindow.cpp and paste it into wavio.cpp.
  • Remove the MainWindow:: prefix
  • Add this include to mainwindow.cpp
#ifndef WAVIO_H
#include "wavio.h"
#endif

Build and run
It needs to work.

Implement readWav()

This function will be declared in wavio.h and defined in wavio.cpp. The following sections provide information you'll need to understand to implement readWav().

Note:
The source code for sndfile.h and sndfile.hh can be accessed directly in the Qt Creator Edit panel.

The SF_INFO struct

The SF_INFO struct declared in sndfile.h holds data about the type of audio file being read or written. libsndfile handles many more file types than the wav file type we're using.

sndfile.h

SF_INFO struct in sndfile.h

/* A pointer to a SF_INFO structure is passed to sf_open () and filled in.
   On write, the SF_INFO structure is filled in by the user and passed into sf_open ().
*/

struct SF_INFO
{
  sf_count_t	frames ;		/* Used to be called samples.  Changed to avoid confusion. */
  int			samplerate ;
  int			channels ;
  int			format ;
  int			sections ;
  int			seekable ;
} ;

typedef	struct SF_INFO SF_INFO ;

sndfile.hh

This file is a C++ class wrapper around data and functions declared in sndfile.h. The Sndfile.hh interface and implementation are combined in this one file. Sndfile.hh only exposes four of the six data members of the SF_INFO struct: frames, format, channels, and samplerate.

The SF_INFO struct is declared private in the SndfileHandle class.

class SndfileHandle
{ private :
    struct SNDFILE_ref
    { SNDFILE_ref (void) ;
      ~SNDFILE_ref (void) ;

      SNDFILE *sf ;
      SF_INFO sfinfo ;
      int ref ;
    } ;

    SNDFILE_ref *p ;

  public :

Four of the six SF_INFO data variables can be accessed using these functions.

sf_count_t  frames (void) const   { return p ? p->sfinfo.frames : 0 ; }
int     format (void) const   { return p ? p->sfinfo.format : 0 ; }
int     channels (void) const { return p ? p->sfinfo.channels : 0 ; }
int     samplerate (void) const { return p ? p->sfinfo.samplerate : 0 ; }

Some of the above code may look a little strange if you're unaware of this usage.

int format (void)
// is the same as
int format()

return (a > b)  ? true : false;
// means
if (a > b)
    return true;
else
    return false;
sndfile.hh code notes
  • There is no sndfile.cpp. The implementation follows the SndfileHandle class declaration.
  • The public section of the class contains several overrides of these four functions
    • read() for types short, int, float, double, with one one sample per frame
    • write() for types short, int, float, double, with one one sample per frame
    • readf() for types short, int, float, double, with more one one sample per frame like stereo or surround
    • writef() for types short, int, float, double, with more one one sample per frame like stereo or surround

Using std::cout in a Qt application

  • #include <QDebug>
  • qDebug() << whatever;

Here's the constructor overload of SndfileHandle you should use in readWav().

SndfileHandle (std::string const & path, int mode = SFM_READ,
        int format = 0, int channels = 0, int samplerate = 0) ;

Because four of the five parameters are default initialized you can call it with a single parameter.

SndfileHandle (std::string const & path) ;

Append readWav() at the end of the on_actionOpen_triggered() function in mainwindow.cpp

Build and run
Choose Open from the File menu for each of the four files in hw81_wav. The Application Output tab at the bottom of the screen should display these results as each file is opened.

/Users/je/cs312/_cs312/cs312/hw81/hw81_wav/cs312.wav
frames  134144
format  65542
channels  1
samplerate  44100
nRead  134144
/Users/je/cs312/_cs312/cs312/hw81/hw81_wav/sine_pcm_float32.wav
frames  22050
format  65542
channels  1
samplerate  44100
nRead  22050
/Users/je/cs312/_cs312/cs312/hw81/hw81_wav/sine_pcm16.wav
frames  22050
format  65538
channels  1
samplerate  44100
nRead  22050
/Users/je/cs312/_cs312/cs312/hw81/hw81_wav/sine_pcm24.wav
frames  22050
format  65539
channels  1
samplerate  44100
nRead  22050

Implement writeWav()

Task 4: Save a vector of samples to a wav file
  • vsamps was filled in the readWav() function in /mainwindow.cpp
  • Call writeWav( qSaveAsFileName.toStdString(), vsamps ) from on_actionSave_As_triggered() in mainwindow.cpp.
  • Use a new name for the Save As file.
  • Remove the doMessageBox( qSaveAsFileName ) statement from on_actionSave_As_triggered() in mainwindow.cpp.

Notes
SFM_READ is used when reading a file.
SFM_WRITE is used when writing a file.
I used this SndfileHandle constructor from Sndfile.hh in writeWav().

SndfileHandle (std::string const & path, int mode = SFM_WRITE) ;

Links
http://www.cplusplus.com/forum/general/38854/
https://parumi.wordpress.com/2007/12/16/how-to-write-wav-files-in-c-using-libsndfile/ (corrupted web page - see qtcentre next)
https://www.qtcentre.org/threads/34264-Wav-Files (17th September 2010, 16:09 response)
https://github.com/erikd/libsndfile/blob/master/examples/sndfilehandle.cc

Implement Play

Task 5: on_pushButton_Play_clicked()

Consult https://doc.qt.io/qt-5/audiooverview.html

I used the first example. Set the volume to your liking.

Test Opening, Saving and Playing a wav file.
  • Open a wav file.
  • Play the wav file.
  • Save the wav file with a new name passing in the sample vector returned when you opened the file.
  • Open the newly saved file.
  • Play it.

Do not continue until these three slots have been implemented and are working. Knowing the qOpenFileName and qSaveAsFileName is required for the functions in /hw812 and hw813.

hw812_qtwavio_stats

The next step it to create the wavstats.h and wavstats.cpp files that will display information and statistics about the file that was opened.

Statistics from the samples of an audio file help audio researchers, mixing and mastering professionals, audio hardware and software developers, and individual music producers compare, reproduce, and judge the quality of an audio file.

This assignment will calculate and display information and statistics about a wav file.

Setup

hw81200.png

Execute in Terminal

cd $HOME312/cs312/hw81
cp -R hw811_qtwavio/ hw812_qtwavio_stats
cd hw812_qtwavio_stats
rm -fR build-debug # from hw811
mkdir build-debug
mv hw811_qtwavio.pro hw812_qtwavio_stats.pro
rm -f hw811_qtwavio.pro.user
rm -f *.wav
sed -i '' s/hw811_qtwavio.pro/hw812_qtwavio_stats.pro/g hw812_qtwavio_stats.pro
touch wavstats.h
touch wavstats.cpp

Important
Change the Qt build directory to hw812_qtwavio_stats/build-debug in the Projects panel.

Open hw812_qtwavio_stats.pro in Qt Creator.

Add wavstats.h and wavstats.cpp to the SOURCES section

SOURCES += \
 main.cpp \
 mainwindow.cpp \
 wavio.cpp \
 wavstats.cpp
HEADERS += \
 mainwindow.h \
 $${LSF}/sndfile.hh \
 wavio.h \
 wavstats.h

Run qmake from the Build menu.

wavstats.h and wavstats.cpp should appear in the Edit panel.

Display the SF_INFO data in the left QPlainTextEdit panel plainTextEdit_sfinfo.

wavstats.h
Copy/paste

#ifndef WAVSTATS_H
#define WAVSTATS_H

#ifndef SNDFILE_HH
#include "sndfile.hh"
#endif

#ifndef WAVIO_H
#include "wavio.h"
#endif

#include <QPlainTextEdit>
#include <QString>

#include <vector>

typedef double MY_TYPE;
#define FORMAT RTAUDIO_FLOAT64

const int FS = 44100;

extern SF_INFO mysfinfo;
extern std::vector<double> vsamps;

extern void displaySF_INFO( QPlainTextEdit* pte );
extern void displayStats( QPlainTextEdit* pte );

#endif //

wavstats.cpp
The purpose of this file is to show you how to display different format types in a QPlainTextEdit widget. You will be using these format techniques when you display the real SF_INFO in the app.

Copy/paste

#ifndef WAVSTATS_H
#include "wavstats.h"
#endif

#include <QTextStream>

#include <cmath> // for log10
#include <sstream>


SF_INFO mysfinfo;

void displaySF_INFO( QPlainTextEdit* pte )
{
    /*
     * EXAMPLE ONLY
     * More info at
     * https://doc.qt.io/qt-5/qstring.html
     * https://doc.qt.io/qt-5/qtextstream.html
     */

    QString s1 = "s1 Just text, no numbers";
    pte->appendPlainText( s1 );

    QString s2;
    QTextStream out( &s2 );
    int nRead( 123456 );
    out << "s2 nRead = " << nRead;
    pte->appendPlainText( s2 );

    QString s3 = "s3 vsize = ";
    QString ns;
    int vsize = 2468;
    ns.setNum( vsize );
    s3 = s3 + ns;
    pte->appendPlainText( s3 );

    int samplerate = 44100;
    QString s4 = QString( "s4 samplerate = %1" ).arg( samplerate );
    pte->appendPlainText( s4 );

    double k2PI = 6.2831853;
    QString s5 = QString( "s5 k2PI = %1" ).arg( k2PI, 0, 'f', 6 );
    pte->appendPlainText( s5 );

    int num = 65536;
    QString s6 = QString( "s5 65536 is %1 hex " ).arg( num, 0, 16 );
    pte->appendPlainText( s6 );

    std::ostringstream oss;
    oss << "s7\tnRead = " << nRead << "\n\tvsize = " << vsize << "\n\tsamplerate = " << samplerate;
    QString s7( oss.str().c_str() );
    pte->appendPlainText( s7 );
}

// Utility calculations
MY_TYPE calc_dB( MY_TYPE f1, MY_TYPE f2 )
{
    // you write
    // see class web page
}

MY_TYPE getMinSample( const std::vector<MY_TYPE>& v )
{
    MY_TYPE x{0};
    // you write
    // see class web page
    return x;
}

MY_TYPE getMaxSample( const std::vector<MY_TYPE>& v )
{
    MY_TYPE x{0};
    // you write
    // see class web page
    return x;
}

MY_TYPE getRMSamplitude( const std::vector<MY_TYPE>& v )
{
    MY_TYPE mn{0};
    // you write
    // see class web page
    return mn;
}

// Display calculations
void doCalcNumSamples( QPlainTextEdit* pte )
{
    // you write
    // see class web page
}

void doCalcLengthSeconds( QPlainTextEdit* pte )
{
    // you write
    // see class web page
}

void doCalcDCOffset( QPlainTextEdit* pte )
{
    // you write
    // see class web page
}

void doCalcMinSample( QPlainTextEdit* pte )
{
    // you write
    // see class web page
}

void doCalcMaxSample( QPlainTextEdit* pte )
{
    // you write
    // see class web page
}

void doCalcMaxSample_dB( QPlainTextEdit* pte )
{
    // you write
    // see class web page
}

void doCalcRMSAmplitude( QPlainTextEdit* pte )
{
    // you write
    // see class web page
}

void doCalcPeakCount( QPlainTextEdit* pte )
{
    // you write
    // see class web page
}

void doCountClipping( QPlainTextEdit* pte )
{
    // you write
    // see class web page
}

void displayStats( QPlainTextEdit* pte )
{
    doCalcNumSamples( pte );
    doCalcLengthSeconds( pte );
    doCalcDCOffset( pte );
    doCalcMinSample( pte );
    doCalcMaxSample( pte );
    doCalcMaxSample_dB( pte );
    doCalcRMSAmplitude( pte );
    doCalcPeakCount( pte );
    doCountClipping( pte );
}

mainwindow.cpp

  • Add includes for wavstats.h with appropriate header guards.
  • Append displaySF_INFO() to on_actionOpen_triggered()
vsamps = readWav( qOpenFileName.toStdString() );
displaySF_INFO( ui->plainTextEdit_sfinfo );

Build and run

Demo output
This information is displayed using the code above. Remember that code is for instructional purposes showing you how to convert different data types to a QString that can be displayed in a QPlainTextEdit panel.

hw81205.png

This is what you want to see when you open cs312.wav.

hw81117.png

Recall from hw811 that the cs312.wav file produced this output using the qDebug() function.

hw81206.png

You've already got all information you need. However, you'll need to display the format variable in hex to be able to interpret it correctly.

Here's the relevant section from sndfile.h.

enum
{ /* Major formats. */
  SF_FORMAT_WAV     = 0x010000,   /* Microsoft WAV format (little endian default). */
  // many more types

  /* Subtypes from here on. */

  SF_FORMAT_PCM_S8    = 0x0001,   /* Signed 8 bit data */
  SF_FORMAT_PCM_16    = 0x0002,   /* Signed 16 bit data */
  SF_FORMAT_PCM_24    = 0x0003,   /* Signed 24 bit data */
  SF_FORMAT_PCM_32    = 0x0004,   /* Signed 32 bit data */

  SF_FORMAT_PCM_U8    = 0x0005,   /* Unsigned 8 bit data (WAV and RAW only) */

  SF_FORMAT_FLOAT     = 0x0006,   /* 32 bit float data */
  SF_FORMAT_DOUBLE    = 0x0007,   /* 64 bit float data */
// many more subtypes
} ;

The cs312.wav format is reported as 65542 decimal or 0x10006. If you look at the above table that's (SF_FORMAT_WAV | SF_FORMAT_FLOAT) using C++'s bitwise OR symbol |.

Implement the correct SF_INFO display in the displayPlainText_sfinfo widget.

sox

A versatile command line program called SoX was developed in the early 1990s and is still in use today. Sox can be downloaded and installed on Mac, Windows, and Linux. It is installed in the course labs.

https://sourceforge.net/projects/sox/files/sox/14.4.2/

Here is sox output of the stat and stats commands for the cs312.wav file. The

# calculations you'll need to implement are indicated by #<==

command: sox cs312.wav -n stat

 Samples read:            134144 #<==
 Length (seconds):      3.041814 #<==
 Scaled by:         2147483647.0
 Maximum amplitude:     0.485229 #<==
 Minimum amplitude:    -0.501160 #<==
 Midline amplitude:    -0.007965
 Mean    norm:          0.019685
 Mean    amplitude:    -0.000033
 RMS     amplitude:     0.042641 #<==
 Maximum delta:         0.442688
 Minimum delta:         0.000000
 Mean    delta:         0.014828
 RMS     delta:         0.035618
 Rough   frequency:         5862
 Volume adjustment:        1.995

 command:  sox cs312.wav -n stats
 DC offset  -0.000033 #<==
 Min level  -0.501160
 Max level   0.485229
 Pk lev dB      -6.00 #<==
 RMS lev dB    -27.40
 RMS Pk dB     -18.05
 RMS Tr dB     -56.71
 Crest factor   11.75
 Flat factor     0.00
 Pk count           2 #<==
 Bit-depth      16/16
 Num samples     134k
 Length s       3.042
 Scale max   1.000000
 Window s       0.050

When you open the cs312.wav file your calculations should closely match the sox readings above.

hw81204a.png

  hw812 calc sox
Samples read: 134144 134144
Length (seconds): 3.041814 3.041814
DC offset -0.000033 -0.000033
Minimum amplitude: -0.501160 -0.501160
Maximum amplitude: 0.485229 0.485229
Pk lev dB -6.000478 -6.00
RMS amplitude: 0.042641 0.042641
Pk count 2 2
Clipped samples 0 n/a
Stats calculations

These pages from the sox manual describe the formulas used. hw81201.png

hw81202.png

The displayStats(…) function
void displayStats( QPlainTextEdit* pte )
{
    doCalcNumSamples( pte );
    doCalcLengthSeconds( pte );
    doCalcDCOffset( pte );
    doCalcMinSample( pte );    // Min level
    doCalcMaxSample( pte );    // Max level
    doCalcMaxSample_dB( pte ); //Peak level dB
    doCalcRMSAmplitude( pte );
    doCalcPeakCount( pte );
    doCountClipping( pte );
}
Calculation hints

These helper functions should be written first because they can be used by more than one of the functions below.

  • calc_dB( MY_TYPE f1, MY_TYPE f2 )
  • getMinSample( const std::vector<MY_TYPE>& v )
  • getMaxSample( const std::vector<MY_TYPE>& v )
  • getRMSamplitude( const std::vector<MY_TYPE>& v )


    RMS is Root Mean Square or the square root of the average of every sample squared.

    doCalcNumSamples( pte )
    vsamps.size()
    doCalcLengthSeconds( pte )
    length_of_one_sample_in_seconds * numSamples
    doCalcDCOffset( pte )
    average sum of all samples postitive and negative
    doCalcMinSample( pte )
    search for minimum sample
    doCalcMaxSample( pte )
    search for maximum sample
    doCalcMaxSample_dB( pte )
    convert maxPeak sample to dB
    The decibel formula can be found on pages 10-13 of Digital Audio Fundamentals.pdf
    doCalcRMSAmplitude( pte )
    Root Mean Square average
    square root of the average of every sample squared
    doCalcPeakCount( pte )
    find the maximum sample value
    find the minimum sample value
    search all samples and count number of matches to either max or min
    doCountClipping( pte )
    search through all samples and count number of times sample > 1.0 or sample < -1.0

hw813_qtwavio_plot

In this assignment we're going to plot the WAV file we opened.

Setup

Close all projects in Qt.

Execute these commands in Mac Terminal.

cd ~/cs312/hw81from83
cp -R hw812_qtwavio_stats/ hw813_qtwavio_plot
cd hw813_qtwavio_plot
rm -fR build-debug/*
mv hw812_qtwavio_stats.pro hw813_qtwavio_plot.pro
mv hw812_qtwavio_stats.pro.user hw813_qtwavio_plot.pro.user
sed -i '' s/hw812_qtwavio_stats.pro/hw813_qtwavio_plot.pro/g hw813_qtwavio_plot.pro

Important
Change the Qt build directory to hw812_qtwavio_stats/build-debug in the Projects panel.

Download QCustomPlot widget

This is a 3rd party widget that you will have to download.

Open this web page

https://www.qcustomplot.com/index.php/download

Download this file

Direct link: https://www.qcustomplot.com/release/2.0.1/QCustomPlot.tar.gz

hw81315.png

Double click to extract the qcustomplot folder from QCustomPlot.tar.gz

Move the qcustomplot folder to your $HOME312 folder.

Make sure you see this folder contents in ~/cs312/qcustomplot

hw81301.png

Execute these commands in Terminal to create a $HOME312/common/qcustomplot folder and move qcustomplot.cpp and qcustomplot.h into it.

cd $HOME312/common
mkdir qcustomplot
cd qcustomplot
mv $HOME312/qcustomplot/qcustomplot.cpp .
mv $HOME312/qcustomplot/qcustomplot.h .

Open hw813_qtwaveio_plot.pro in QtCreator

Make the changes shown in red.

hw813_qtwavio_plot.pro.pdf

Save the file.

Choose Clean All and Run qmake from the Build menu.

When qmake finishes your project folder should look like this.

hw81302.png

Add more widgets

Open mainwindow.ui in Design view.

Resize mainwindow to these dimensions.

hw81304.png

The current arrangement can be improved.

hw81303.png

Widen and the two QPlainTextEdit widgets and center the Stats label.

hw81303a.png

Add a Widget widget.

hw81305.png

Drag it into the main window form.

hw81306.png

Name it customPlot and use these dimensions.

hw81307.png

Add a Zoom text label and an horizontalScrollbar (not horizontalSlider).

hw81308.png

The horizontalScrollBar has these dimensions.
Name it horizontalScrollBar_zoom.

hw81309.png

Use these settings.

hw81317.png

Here are the variable names I assigned to these widgets.

hw81314.png

Right click the horizontalSlider_zoom widget and choose Go to slot.
Select the valueChanged(int) signal.

hw81318.png

Promote QWidget to QCustomPlot

These instructions are from the qcustomplot web page.

https://www.qcustomplot.com/index.php/tutorials/settingup.

Right click the QWidget widget and choose Promote to…

hw81310.png

Type QCustomPlot for the Promoted class name. The header file should automatically fill in.

hw81311.png

Click the Add button and another dialog will appear.
Select the QCustomPlot widget and click the Promote button.
Leave the Global include checkbox unchecked.

hw81312.png

Run the program

I often do these steps from the Build menu before running a program that was a modification of another program.

  • Clean All
  • Run qmake
  • Rebuild all
  • Click the Run button

If everything worked you'll see this.

hw81316.png

Open the cs312.wav file.

The SF_INFO and Stats panels should work as before.

hw81319.png

There are two tasks remaining

  1. Increase the number of decimals in the Stats panel.
  2. Plot the waveform.
Code Changes

wavestats.cpp
The floating point statistics are displayed using a statement like this.

qs = QString( "DC offset: %1" ).arg( sum, 0, 'f', 6 );

We'll use Find/Replace. Type Command-F to display the Find/Replace panel.
Enter these terms:
Find: 'f', 6
Replace: 'f', 14

hw81320.png

Note:
The options panel was displayed by clicking the magnifying glass icon.

Build and Run
You should see this.

hw81323.png

Display a plot

mainwindow.h
These are the changes you'll need.

hw813_mainwindow_h.pdf

mainwindow.cpp
I used the first code example on this page as a guide.

https://www.qcustomplot.com/index.php/tutorials/basicplotting

// generate some data:
QVector<double> x(101), y(101); // initialize with entries 0..100
for (int i=0; i<101; ++i)
{
  x[i] = i/50.0 - 1; // x goes from -1 to 1
  y[i] = x[i]*x[i]; // let's plot a quadratic function
}
// create graph and assign data to it:
customPlot->addGraph();
customPlot->graph(0)->setData(x, y);
// give the axes some labels:
customPlot->xAxis->setLabel("x");
customPlot->yAxis->setLabel("y");
// set axes ranges, so we see all data:
customPlot->xAxis->setRange(-1, 1);
customPlot->yAxis->setRange(0, 1);
customPlot->replot();

Lets try it.
Add this function to mainwindow.cpp. It's the same as above with ui-> prefix added to customPlot.

void MainWindow::plot()
{
    // generate some data:
    QVector<double> x( 101 ), y( 101 ); // initialize with entries 0..100
    for ( int i = 0; i < 101; ++i )
    {
        x[ i ] = i / 50.0 - 1;    // x goes from -1 to 1
        y[ i ] = x[ i ] * x[ i ]; // let's plot a quadratic function
    }
    // create graph and assign data to it:
    ui->customPlot->addGraph();
    ui->customPlot->graph( 0 )->setData( x, y );
    // give the axes some labels:
    ui->customPlot->xAxis->setLabel( "x" );
    ui->customPlot->yAxis->setLabel( "y" );
    // set axes ranges, so we see all data:
    ui->customPlot->xAxis->setRange( -1, 1 );
    ui->customPlot->yAxis->setRange( 0, 1 );
    ui->customPlot->replot();
}

Build and Run
Open a WAV file. You should see this.

hw81324.png

plot( ) function You'll need to modify the plot() function to graph the WAV file. The data you need is contained in the vsamps vector when you open a file.

QCustomPlot requires the data points to be stored in a QVector of double.
vsamps is a std::vector of double.

I've added comments to the code to help get you started.

 // generate some data:

 /* JE
 size_t sz = vsamps.size();
 QVector<MY_TYPE> x(sz); // the n sample indices on the x axis = vsamps.size()
 QVector<MY_TYPE> y(sz); // the n sample values on the y axis for each vsamp[n]

The for loop for the x and y axes needs to be revised.

 for ( int i = 0; n < sz; ++i )
 {
  // fill the qx[i] QVector with sample indices
  // fill the qy[i] QVector with sample values
 }
 */

 // create graph and assign data to it:
 customPlot->addGraph();
 customPlot->graph(0)->setData(x, y);


 /* JE
 // give the axes some labels:
 //  customPlot->xAxis->setLabel("x");
 // customPlot->yAxis->setLabel("y");
 */

 /* JE
 // set axes ranges, so we see all data
// set axes ranges, so we see all data:
    ui->customPlot->xAxis->setRange( -1, 1 );
    ui->customPlot->yAxis->setRange( 0, 1 );

 These two lines need to be adjusted based on how much of the waveform is being plotted.
 When a file is first opened the x axis ranges from 0 to sz-1 \\
 and the y axis ranges from -1 t0 +1.

 After that use the on_horizontalScrollBar_zoom_valueChanged( int value ) function change the xAxis range
 */

customPlot->replot();

on_horizontalScrollBar_zoom_valueChanged( ) function The plot data is not resized, only the range of the plot.

void MainWindow::on_horizontalScrollBar_zoom_valueChanged( int value )
{
  // scaling based on vsamps.size() and value parameter
  ui->customPlot->xAxis->setRange( 0, /*scaling*/ );
  ui->customPlot->replot();
}

Plot updates
The plot should be drawn for the full size of the wav file when first opened.
The plot should be redrawn at a new scale when the horizontalScrollBar position is changed.

Output on first open

hw81321.png

Output with zoom
Note the X axis scale change. QCustomPlot does it for free.

hw81322.png

Very few changes and only two new functions need to be added to mainwindow.cpp

hw813_mainwindow_cpp.pdf

Limitations
The zoom always starts from 0 to scaled value.
You cannot zoom and scroll.
You cannot select a portion of the plot.

Submission

Feel free to email jellinge@carleton.edu on any part of the homework that is unclear to you. Chances are if you have questions other students do too. I will answer those questions by email to everyone in the class. The original sender will remain anonymous. I have tried to provide clues to the homework in the web pages, reading assignments, and labs. Sometimes what seems obvious to me is not obvious to students just learning C++.

Create a folder named hwNN_LastnameFirstname_LastnameFirstname.
Substitute your name and your partner's name for LastnameFirstname_LastnameFirstname.

Remember

  • Boilerplate header at top of every file.
  • Make sure your program compiles before submitting.
  • Programs that compile and produce partially correct output will be graded.
  • You can send notes to me in your homework files by enclosing them in block comments.
  • Programs that do not compile get an automatic F (59%).
  • Submit the homework in only one of the partner's folders.
  • Empty your build or build-debug folders using the command emptyBuildFolder.sh
  • Only include .h, .cpp, Makefiles, CMakeLists.txt
  • Do not include these folders/files
    .git
    .vscode
  • Double check for the above two folders using this command

    ls -a hwNN_LastnameFirstname1_LastnameFirstname2
    # if either .git or .vscode exist in the folder you're submitting remove them with
    # rm -fR .git
    # rm -fR .vscode
    

Hand-in folder contents

/common
├── RtAudio
│   ├── RtAudio.cpp
│   └── RtAudio.h
├── RtMidi
│   ├── RtMidi.cpp
│   └── RtMidi.h
├── hw332_CMidiPacket.cpp
├── hw332_CMidiPacket.h
├── hw411_rand_int.cpp
├── hw411_rand_int.h
├── hw421_CDelayMs.cpp
├── hw421_CDelayMs.h
├── hw422_CAppleMidiSynth.cpp
├── hw422_CAppleMidiSynth.h
├── hw423_CMidiTrack.cpp
├── hw423_CMidiTrack.h
├── hw511_CInstrument.cpp
├── hw511_CInstrument.h
├── libsndfile
│   ├── sndfile.h
│   └── sndfile.hh
└── qcustomplot
    ├── qcustomplot.cpp
    └── qcustomplot.h

/hw81
├── hw811_qtwavio
│   ├── build-debug (empty)
│   ├── hw811_qtwavio.pro
│   ├── hw811_qtwavio.pro.user
│   ├── main.cpp
│   ├── mainwindow.cpp
│   ├── mainwindow.h
│   ├── mainwindow.ui
│   ├── wavio.cpp
│   └── wavio.h
├── hw812_qtwavio_stats
│   ├── build-debug (empty)
│   ├── hw812_qtwavio_stats.pro
│   ├── hw812_qtwavio_stats.pro.user
│   ├── main.cpp
│   ├── mainwindow.cpp
│   ├── mainwindow.h
│   ├── mainwindow.ui
│   ├── wavio.cpp
│   ├── wavio.h
│   ├── wavstats.cpp
│   └── wavstats.h
 hw813_qtwavio_plot
 ├── build-debug (empty)
 ├── hw813_qtwavio_plot.pro
 ├── hw813_qtwavio_plot.pro.user
 ├── main.cpp
 ├── mainwindow.cpp
 ├── mainwindow.h
 ├── mainwindow.ui
 ├── wavio.cpp
 ├── wavio.h
 ├── wavstats.cpp
 └── wavstats.h

Author: John Ellinger

Created: 2020-02-26 Wed 22:12

Validate