CS 312 - Week 8.1 Class
CS 312 Audio Programming Winter 2020
Table of Contents
- 8.1 Class
- Homework 8.1
- hw811_qtwavio
- hw812_qtwavio_stats
- hw813_qtwavio_plot
- Submission
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
Download (right click) this file into your hw81 folder.
Double click to unzip.
You should see these four files.
Copy the hw81_wav folder into hw81.
Create a new Qt Widgets Application
Name it hw811_qtwavio and save it in the ~/cs312/hw81from83 folder.
- 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.
Create this mainwindow.ui layout in Designer view
Add a File menu.
- Double click Type-Here
Type File followed by Return
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.
The variable names I used are shown below.
Name the Play button and the labels for SF_INFO and Stats.
Widget action slots
pushButton_Play
Right click the Play button choose Go to slot and choose the triggered(bool) action.
The two menu items
Slots for the Menu items are created in the Action Editor panel.
Right click the actionOpen text and choose Go to slot.
Right click the actionSave_As text and choose Go to slot.
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.
Name it wavio.h.
The Path should already show the hw811_qtwavio folder. If not choose it.
Click Continue.
On the Project Management page choose your version control and click Done.
Qt adds the wavio.h to the project.
You should see the wavio.h file open ready to add code.
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.
Changes to mainwindow.h
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.
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.
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.
- Name the file and click the Save button.
Use the doMessage(…) function to display the Save As filename.
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
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.
This is what you want to see when you open cs312.wav.
Recall from hw811 that the cs312.wav file produced this output using the qDebug() function.
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.
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.
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
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
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.
Save the file.
Choose Clean All and Run qmake from the Build menu.
When qmake finishes your project folder should look like this.
Add more widgets
Open mainwindow.ui in Design view.
Resize mainwindow to these dimensions.
The current arrangement can be improved.
Widen and the two QPlainTextEdit widgets and center the Stats label.
Add a Widget widget.
Drag it into the main window form.
Name it customPlot and use these dimensions.
Add a Zoom text label and an horizontalScrollbar (not horizontalSlider).
The horizontalScrollBar has these dimensions.
Name it horizontalScrollBar_zoom.
Use these settings.
Here are the variable names I assigned to these widgets.
Right click the horizontalSlider_zoom widget and choose Go to slot.
Select the valueChanged(int) signal.
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…
Type QCustomPlot for the Promoted class name. The header file should automatically fill in.
Click the Add button and another dialog will appear.
Select the QCustomPlot widget and click the Promote button.
Leave the Global include checkbox unchecked.
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.
Open the cs312.wav file.
The SF_INFO and Stats panels should work as before.
There are two tasks remaining
- Increase the number of decimals in the Stats panel.
- 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
Note:
The options panel was displayed by clicking the magnifying glass icon.
Build and Run
You should see this.
Display a plot
mainwindow.h
These are the changes you'll need.
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.
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
Output with zoom
Note the X axis scale change. QCustomPlot does it for free.
Very few changes and only two new functions need to be added to mainwindow.cpp
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