CS 312 - Week 7 Class 3
CS 312 Audio Programming Winter 2020

Table of Contents

7.3 Class

Install RtAudio

RtAudio is and open source cross platform audio library. It's written in C++ by the same authors that wrote the RtMidi library. The libsnd file library was designed to read and write sound files. The hw811 homework projects wrote samples one at a time to a file and then saved that file to disk. Any audio processing on that file was done post writing. RtAudio is designed to read and write buffers of samples that are processed in close to real time before being sent to the audio output. The size of the buffers determine the latency involved between the time input is received and output is heard. Latency increases with larger block sizes. The audio processing is done in a callback routine that must be written as efficiently as possible. That means allocating memory, or writing output to disk, and updating the GUI are not recommended except for debugging purposes.

Setup

Open the RtAudio home page.
https://www.music.mcgill.ca/~gary/rtaudio/

Look for the Download section and download Version 5.1.0.
The download file will be named rtaudio-5.1.0.tar.gz

Copy rtaudio-5.1.0.tar.gz to your $HOME312 directory at the same level as your bin folder.
Double click to extract.

cd to your common folder

cd $HOME312/common
mkdir RtAudio

Copy the RtAudio.h and RtAudio.cpp files from the download into the common/RtAudio folder.

cd to your cs312 folder

cd $HOME312/cs312
mkdir hw73
mkdir hw73/hw731_playsaw

Copy the playsaw.cpp file from the downloaded rtaudio/tests folder into the hw73/hw731_playsaw folder.

Rename playsaw.cpp to hw731_playsaw.cpp

mv  playsaw.cpp hw731_playsaw.cpp

Reading

Equal Temperament

The Equal Temperament tuning system we use today was established during the 18th and early 19th centuries. Equal Temperament divides the octave into twelve equal half steps. On the piano the only intervals that are perfectly in tune are octaves, everything else is equally out of tune. Octaves have a frequency ratio of 1:2 and are divided into 12 equal steps. The frequency ratio between each half step is

\[{2^{\frac{1}{{12}}}}\]

In order to calculate the pitch of any note you need a reference frequency and you need to know how many half steps distant it is from that reference. The standard reference pitch is A 440 Hz (called A440) and is MIDI note number 69.

MIDI Note Number To Frequency

The pitch standard in music is 440Hz, the note A above middle C on the piano. Middle C is MIDI note number 60 and A440 is MIDI Note number 69. The frequency of any midiNote is found using the formula below.

\[frequency = 440*{2^{\frac{{midiNote - 69}}{{12}}}}\]

For example Middle C has a frequency of

\[Middle C = 440*{2^{\frac{{60 - 69}}{{12}}}} = 440*{2^{\frac{{ - 9}}{{12}}}} = 261.63Hz\]

Half steps, whole steps, and scales

In musical terms the 12 steps in one octave are called half steps. Two consecutive half steps are called a whole step. Many musical scales are constructed solely out of whole step and half step patterns with the last note of the scale being an octave higher that the first note.

In hw735_qtplaysine_midi2freq you'll create buttons to play the C Major scale whose pattern of whole steps and half steps is WWHWWWH. In MIDI note terms it's the pattern {0,2,4,5,7,9,11,12} plus a starting offset. Instead of using MIDI output you'll have to calculate the frequencies in Hz and generate sine wave tones at that frequency.

Homework 7.3

hw731_playsaw

This is a VScode CMake project.

Create the CMakeLists.txt file

I modified the one from hw732_gensin_phasor.

cmake_minimum_required(VERSION 3.5)

set(PROJECT_NAME hw731_playsaw)
project(${PROJECT_NAME})

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Like RtMidi, RtAudio needs this compiler define  -D__MACOSX_CORE__
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall -D__MACOSX_CORE__")

# Change this path to your common folder
set(COMMON "/Users/je/cs312/_cs312/common")
set(LSF "${COMMON}/libsndfile")
set(ULL "/usr/local/lib")
set (RTA "/Users/je/cs312/_cs312/common/RtAudio")

set(SOURCE_FILES
  ${LSF}/sndfile.hh
  ${RTA}/RtAudio.cpp
  ${PROJECT_NAME}.cpp
  )

add_executable( ${PROJECT_NAME} ${SOURCE_FILES})
target_include_directories(${PROJECT_NAME} PRIVATE ${COMMON} ${LSF} ${RTA})

target_link_libraries(${PROJECT_NAME}
    ${ULL}/libsndfile.1.dylib
    ${ULL}/librtaudio.6.1.0.dylib
    "-framework CoreAudio"
    "-framework CoreFoundation")

Build and run hw731_playsaw

cd build
cmake .. && make && ./hw73_playsaw

Read the usage comments and try again …

./hw731_playsaw 1 44100

and again

./hw731_playsaw 2 44100

Here's what the single channel saw wave looks like in Audacity.

aud8201.png

In the two channel version the frequencies (periods) of each channel are different.

aud8202.png

hw732_playsine

This is a VScode CMake project.

We're going to refactor hw73_playsaw.cpp leaving only essentials necessary to generate and play a mono audio file.

Close any open hw73_playsaw files or projects.

Setup

cd $HOME312/cs312/hw73
cp -r hw731_playsaw hw732_playsine
cd hw732_playsine
mv hw731_playsaw.cpp hw732_playsine.cpp
sed -i '' s/saw/sine/g hw732_playsine.cpp
sed -i '' s/hw731_playsaw/hw732_playsine/g CMakeLists.txt

hw732_playsine.cpp

Open this pdf in new tab.
hw732_playsaw.pdf

Then make these changes. I've colored the text.

Black
leave alone
Red
delete
Blue
replacement text for red text immediately above

CMakeLists.txt
Copy/paste

Build using cmake

Open hw732_playsine folder in VScode.

hw733_playsine_rta

This is a VScode CMake project.

We're going to refactor hw732_playsine.cpp and move all RtAudio related calls to their own rtaudioutils.h and rtaudioutils.cpp files. This will make hw733_playsine_rta.cpp much easier to read.

Setup

cd $HOME312/cs312/hw73
cp -r hw732_playsine hw733_playsine_rta
cd hw733_playsine_rta
touch scratch.cpp
touch rtaudioutils.h
touch rtaudioutils.cpp
mv hw732_playsine.cpp hw733_playsine_rta.cpp
sed -i '' s/hw732_playsine/hw733_playsine_rta/g CMakeLists.txt

hw733_playsine_rta.cpp

Open this pdf in new tab.
hw733_playsine.pdf

Then make these changes. It's different than above. I've colored the text.

Black
leave alone
Red
delete and replace as stated
Blue
Move to scratch.cpp and delete from hw733_playsine_rta.cpp

You might find it easier to move the text to scratch.cpp if you split the VScode window into two panels.

vsc02.png

Save both.

rtaudioutils.h
Copy/paste. This was all stuff that you removed from hw733_playsine_rta.cpp copied to scratch.cpp

#ifndef RTAUDIOUTILS_H_
#define RTAUDIOUTILS_H_
#endif

#ifndef __RTAUDIO_H
#include "RtAudio.h"
#endif
#include <cmath> // for M_PI

// EITHER/OR
typedef float MY_TYPE;
#define FORMAT RTAUDIO_FLOAT32
// #define FORMAT RTAUDIO_FLOAT64
// #define SCALE 1.0
// EITHER/OR END

const int FS = 44100;
const MY_TYPE k2PI = 2 * M_PI;
const MY_TYPE T = 1.0 / FS; // sample period
const MY_TYPE k2PIT = k2PI * T;
const unsigned int kRTABUFFER_SZ = 512;

const unsigned int callbackReturnValue = 1;

extern RtAudio::StreamOptions options;
extern RtAudio::StreamParameters oParams;

struct RTA
{
  // RtAudio stuff
  unsigned int channels;
  unsigned int frameCounter;
  bool checkCount;
  unsigned int nFrames;
  unsigned int bufferFrames;
  unsigned int fs;
  unsigned int device;
  unsigned int offset;

  // constructor
  RTA();
};

extern RTA rta; // declared here, instantiated in globals.cpp
extern void errorCallback(RtAudioError::Type type, const std::string &errorText);

hw733_playsine_rta.cpp
Open hw733_playsine_rta.cpp. Select the Problems tab in the Terminal panel and you'll see eight problems.

vsc01.png

They're related to the RTA struct in rtaudioutils.h and can be fixed by adding the the rta. prefix. When fixed you should be free of errors.

rtaudioutils.cpp
Copy/paste. Most of the hw733_playsine_rta.cpp variables are now part of a class (a struct is a class).

#ifndef RTAUDIOUTILS_H_
#include "rtaudioutils.h"
#endif

RtAudio::StreamOptions options;
RtAudio::StreamParameters oParams;
RTA rta;

// constructor
RTA::RTA() : channels{1},
             frameCounter{0},
             checkCount{false},
             nFrames{0},
             bufferFrames{kBUFFER_SZ},
             fs{FS},
             device{0},
             offset{0}
{
    oParams.deviceId = device;
    oParams.nChannels = channels;
    oParams.firstChannel = offset;

    options.flags = RTAUDIO_HOG_DEVICE;
    options.flags |= RTAUDIO_SCHEDULE_REALTIME;
    options.flags |= RTAUDIO_NONINTERLEAVED;
}
void errorCallback( RtAudioError::Type type, const std::string& errorText )
{
    // This example error handling function does exactly the same thing
    // as the embedded RtAudio::error() function.
    std::cout << "in errorCallback" << std::endl;
    if ( type == RtAudioError::WARNING )
        std::cerr << '\n'
                  << errorText << "\n\n";
    else if ( type != RtAudioError::WARNING )
        throw( RtAudioError( errorText, type ) );
}

hw733_playsine_rta.cpp
Should be ok.

CMakeLists.txt
You'll need to add the rtaudioutils.cpp source file.

Build and run using cmake

You should hear a sine wave at 440Hz.

Once you get the program running add this callback function to hw733_playsine_rta.cpp.

int sineSweep(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames,
         double streamTime, RtAudioStreamStatus status, void *userData)
{
  MY_TYPE *buffer = (MY_TYPE *)outputBuffer;
  if (status)
    std::cout << "Stream underflow detected!" << std::endl;
  MY_TYPE freq = 440.0;
  MY_TYPE amp = 1.0;
  static MY_TYPE phz = 0; // must be static
  MY_TYPE phzinc = k2PIT * freq;
  // add a new static variable that retains its value across multiple callbacks
  static MY_TYPE tmpfreq{freq};

  // //phase increment formula
  for (uint32_t i = 0; i < nBufferFrames; i++)
  {
    phzinc = k2PIT * (freq + tmpfreq);
    tmpfreq += 0.01;
    if (tmpfreq > freq * 3)
      tmpfreq = freq;
    *buffer++ = amp * sin(phz);
    phz += phzinc;
    if (phz >= k2PI)
      phz -= k2PI;
  }
  rta.frameCounter += nBufferFrames;
  if (rta.checkCount && (rta.frameCounter >= rta.nFrames))
    return callbackReturnValue;
  return 0;
}

Then in main() change this

//  dac.openStream(&oParams, NULL, FORMAT, rta.fs, &rta.bufferFrames, &sine, (void *)data, &options, &errorCallback);
    dac.openStream(&oParams, NULL, FORMAT, rta.fs, &rta.bufferFrames, &sineSweep, (void *)data, &options, &errorCallback);

Build and run again

Experiment
Experiment with freq, tmpfreq, phz variables in the callback function and you can get some pretty weird sounds

hw734_qtplaysin

This is a QtCreator qmake project.

Setup

Create a new QT Widgets Application project.

qt8301.png

Name it hw734_qtplaysin and save it in ~/cs312/hw73

qt8302.png

Use qmake build

qt8303.png

Accept MainWindow defaults

qt8304.png

Use Desktop…clang 64bit Kit

qt8305.png

Select <None> for version control and then click Done.

qt8306.png

Open Mac Terminal and execute these commands

cd $HOME312/cs312/hw73/hw734_qtplaysine
mkdir build-debug

Open the Projects view and add the build-debug folder to $HOME312/cs312/hw73/hw734_qtplaysin.

qt8306a.png

Folder layout

qt8306b.png

Create this form

Back to Edit view. Open Forms mainwindow.ui and create this layout.
Hint:
Use the arrow keys to align widgets to the grid.

qt8307.png

Assign these variable names to the widgets

qt8307a.png

qt8308.png

Set the min/max amplitude range and value for horizontalSlider_freq.
These values will used in code and be displayed 20-4000 the label_freqValue.

qt8309.png

Set the min/max amplitude range and value for horizontalSlider_amp.
These values will converted to 0-1.0 in your code and displayed as 0-1.0 in the label_ampValue.

qt8310.png

Use the default settings for the Play and Stop buttons.

Build and run

Click the Run button in Qt Creator.
It should compile, run and display then MainWindow.

Next step is to make it do something.

Add hw732_playsine code

cd $HOME312/cs312/hw73/hw734_qtplaysine
cp $HOME312/cs312/hw73/hw733_playsine_rta/rtaudioutils.cpp .
cp $HOME312/cs312/hw73/hw733_playsine_rta/rtaudioutils.h .

Modify hw734_qtplaysine.pro

Open hw734_qtplaysine.pro in a new tab.
The lines in red are needed. Copy/replace.
This is the makefile used by qmake.

Clean and run qmake

This is recommended after you modify a .pro file.
Choose Clean All from the Build menu.
Choose Run qmake from the Build menu.

Build and run

It should still compile and display MainWindow.

Add/move code to mainwindow.cpp

mainwindow.cpp
Add this code below #include "ui_mainwindow.h". It's code copied directly from hw733_playsine_rta.cpp.

#ifndef RTAUDIOUTILS_H_
#include "rtaudioutils.h"
#endif

#include <iostream>
#include <cmath> // for M_PI

// One-channel sine wave generator replaces saw callback function
int sine(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames,
         double streamTime, RtAudioStreamStatus status, void *userData)
{
  MY_TYPE *buffer = (MY_TYPE *)outputBuffer;
  if (status)
    std::cout << "Stream underflow detected!" << std::endl;
  static MY_TYPE phz = 0;
  MY_TYPE freq = 440.0;
  MY_TYPE amp = 1.0;
  // //phase increment formula
  const MY_TYPE phzinc = k2PIT * freq;
  for (uint32_t i = 0; i < nBufferFrames; i++)
  {
    *buffer++ = amp * sin(phz);
    phz += phzinc;
    if (phz >= k2PI)
      phz -= k2PI;
  }
  rta.frameCounter += nBufferFrames;
  if (rta.checkCount && (rta.frameCounter >= rta.nFrames))
    return callbackReturnValue;
  return 0;
}

Add this code into main(…) directly below ui->setupUi( this );. It's code copied directly from hw733_playsine_rta.cpp.

  RtAudio dac;
  if (dac.getDeviceCount() < 1)
  {
    std::cout << "\nNo audio devices found!\n";
    exit(1);
  }

  MY_TYPE *data = (MY_TYPE *)calloc(rta.channels, sizeof(MY_TYPE));

  // Let RtAudio print messages to stderr.
  dac.showWarnings(true);

  if (rta.device == 0)
    oParams.deviceId = dac.getDefaultOutputDevice();

  try
  {
    dac.openStream(&oParams, NULL, FORMAT, rta.fs, &rta.bufferFrames, &sine, (void *)data, &options, &errorCallback);
    // dac.openStream(&oParams, NULL, FORMAT, rta.fs, &rta.bufferFrames, &sineSweep, (void *)data, &options, &errorCallback);
    dac.startStream();
  }
  catch (RtAudioError &e)
  {
    e.printMessage();
    goto cleanup;
  }

  char input;
  //std::cout << "Stream latency = " << dac.getStreamLatency() << "\n" << std::endl;
  std::cout << "\nPlaying ... press <enter> to quit (buffer size = " << rta.bufferFrames << ").\n";
  std::cin.get(input);

  try
  {
    // Stop the stream
    dac.stopStream();
  }
  catch (RtAudioError &e)
  {
    e.printMessage();
  }

cleanup:
  if (dac.isStreamOpen())
    dac.closeStream();
  free(data);

Build and Run
You should hear the sine wave.

But there's two big problems.

  1. You cannot see the GUI window
  2. You cannot type return to quit.

The only way to quit is by clicking this button in QtCreator.

qt8315.png

The fix for problem 1 is to comment out these lines in main()

//    char input;
//    //std::cout << "Stream latency = " << dac.getStreamLatency() << "\n" << std::endl;
//    std::cout << "\nPlaying ... press <enter> to quit (buffer size = " << rta.bufferFrames << ").\n";
//    std::cin.get( input );

The window appeared but the sound disappeared. The reason for no sound can be found by tracing through the code in the Debugger. You'll discover that dac.startStream() is called and immediately afterwards dac.stopStream() is called. There's no time to process the callback function.

The fix involves refactoring the RtAudio code in main() into functions that can be called as needed outside of main(). These are the RtAudio functions you'll need.

  • openStream(…)
  • closeStream()
  • startStream()
  • stopStream()
  • isStreamOpen()
  • getDefaultOutputDevice()

Here's how I'd organize the calls.

  • Call open_dac_stream() (you implement it) once in the MainWindow constructor but don't call startStream().
  • Call closeStream() in the MainWindow destructor but first check isStreamOpen().
  • Call startStream() only when the Play button is clicked.
  • Call stopStream() only when the Stop button is clicked.

Then you'll still need to deal with the frequency and amplitude sliders and value labels.

FYI
If you're using a third party USB audio interface you might need to find it's ID. This code came from one of the RtAudio examples. You can easily examine the DeviceInfo struct in the Debugger.

unsigned int numDev = dac.getDeviceCount();
RtAudio::DeviceInfo di;
for ( unsigned int i = 0; i < numDev; ++i )
{
    // use the Debugger if you need to know deviceID
    di = dac.getDeviceInfo( i );
}
if ( !isPlaying )
    return;

mainwindow.h
Copy/paste

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#ifndef RTAUDIOUTILS_H_
#include "rtaudioutils.h"
#endif

QT_BEGIN_NAMESPACE
namespace Ui
{
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

  private:
    Ui::MainWindow* ui;

    RtAudio dac;
    void init_controls();
    void open_dac_stream();
};
#endif // MAINWINDOW_H

mainwindow.cpp
Here's some skeleton code to get you started. Copy/replace/paste.

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

#ifndef RTAUDIOUTILS_H_
#include "rtaudioutils.h"
#endif

#include <cmath> // for M_PI
#include <iostream>

/*-------------------------------------------------
 two new variables
-------------------------------------------------*/
RtAudio dac;
bool isPlaying = true;

// One-channel sine wave generator replaces saw callback function
// unchanged
int sine( void* outputBuffer, void* inputBuffer, unsigned int nBufferFrames,
          double streamTime, RtAudioStreamStatus status, void* userData )
{
    MY_TYPE* buffer = ( MY_TYPE* )outputBuffer;
    if ( status )
        std::cout << "Stream underflow detected!" << std::endl;
    static MY_TYPE phz = 0;
    MY_TYPE freq = 440.0;
    MY_TYPE amp = 1.0;
    // //phase increment formula
    const MY_TYPE phzinc = k2PIT * freq;
    for ( uint32_t i = 0; i < nBufferFrames; i++ )
    {
        *buffer++ = amp * sin( phz );
        phz += phzinc;
        if ( phz >= k2PI )
            phz -= k2PI;
    }
    rta.frameCounter += nBufferFrames;
    if ( rta.checkCount && ( rta.frameCounter >= rta.nFrames ) )
        return callbackReturnValue;
    return 0;
}

MainWindow::MainWindow( QWidget* parent )
    : QMainWindow( parent ), ui( new Ui::MainWindow )
{
    ui->setupUi( this );
/*-------------------------------------------------
 init_controls()
 open_dac_stream() but don't call startStream()
-------------------------------------------------*/
}

MainWindow::~MainWindow()
{
    delete ui;
/*-------------------------------------------------
if dac.isStreamOpen() then close it
-------------------------------------------------*/
}

void MainWindow::init_controls()
{
/*-------------------------------------------------
set these controls to their default values
horizontalSlider_freq
horizontalSlider_amp
label_freqValue
label_ampValue
-------------------------------------------------*/
}

void MainWindow::open_dac_stream()
{
/*-------------------------------------------------
figure it out
don't forget about QApplication::processEvents();
-------------------------------------------------*/
}

void MainWindow::close_dac_stream()
{
/*-------------------------------------------------
figure it out
-------------------------------------------------*/
}

// Use right-click "Go to Slot" on the control in Design view
void MainWindow::on_pushButton_play_clicked()
{
/*-------------------------------------------------
figure it out
-------------------------------------------------*/
}

// Use right-click "Go to Slot" on the control in Design view
void MainWindow::on_pushButton_stop_clicked()
{
/*-------------------------------------------------
figure it out
-------------------------------------------------*/
}

// Use right-click "Go to Slot" on the control in Design view
void MainWindow::on_horizontalSlider_freq_valueChanged( int value )
{
/*-------------------------------------------------
figure it out
remember to update label_freqValue whenever the slider moves
-------------------------------------------------*/
}

// Use right-click "Go to Slot" on the control in Design view
void MainWindow::on_horizontalSlider_amp_valueChanged( int value )
{
/*-------------------------------------------------
figure it out
remember to update label_ampValue whenever the slider moves
remember code and label_ampValue use/display values as 0.0-1.0
-------------------------------------------------*/
}

When you're finished the frequency and amplitude sliders should work smoothly without any static, pops, or clicks in the sound. The value labels should display the value of the sliders as they are moved.

Last step
Fix as many yellow warnings as you can. Ideally you'll end up with none.
Word of advice. Fix things one at a time and test.

hw735_qtplaysine_midi2freq

Setup

cd $HOME312/cs312/hw73
cp -R hw734_qtplaysine hw735_qtplaysine_midi2freq
cd $HOME312/cs312/hw73/hw735_qtplaysine_midi2freq
mv hw734_qtplaysine.pro hw735_qtplaysine_midi2freq.pro
rm -f hw734_qtplaysine.pro.user
emptyBuildFolder.sh
cd build-debug
rm -f .qmake.stash
cd ..

Open the Project view and add the build-debug folder to ~/cs312/hw73/hw735qtplaysin_midi2freq.

Create this form

Open Forms mainwindow.ui.
Delete all widgets.
Create this new layout.

qt82501.png

The "piano keys" were created using the Tool Button widget.

qt82501a.png

Assign these variable names to the Widgets

qt82502.png

Settings for verticalSlider_amp.

qt82503.png

qt82504.png

Use the default settings for the Quit button.

Build

Add/remove functions, rename variables, create new files, whatever it takes to get the program to compile and run.

Program requirements

  • Audio begins at the correct frequency when you press and hold the mouse button down on a "piano key" button.
  • Audio stops when you release the mouse button or move out of the button.
  • Amplitude slider works as before.
  • Quit button exits the application. Hint, use the Help tool or Google to look up QApplication functions.

Submission Format

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

$HOME312/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

$HOME312/cs312/hw73
├── hw731_playsaw
│   ├── CMakeLists.txt
│   ├── build
│   └── hw731_playsaw.cpp
├── hw732_playsine
│   ├── CMakeLists.txt
│   ├── build
│   └── hw732_playsine.cpp
├── hw733_playsine_rta
│   ├── CMakeLists.txt
│   ├── build
│   ├── hw733_playsine_rta.cpp
│   ├── rtaudioutils.cpp
│   ├── rtaudioutils.h
│   └── scratch.cpp
├── hw734_qtplaysine
│   ├── build-debug
│   ├── hw734_qtplaysine.pro
│   ├── hw734_qtplaysine.pro.user
│   ├── main.cpp
│   ├── mainwindow.cpp
│   ├── mainwindow.h
│   ├── mainwindow.ui
│   ├── rtaudioutils.cpp
│   └── rtaudioutils.h
└── hw735_qtplaysine_midi2freqJE
    ├── build-debug
    ├── hw735_qtplaysine_midi2freq.pro
    ├── hw735_qtplaysine_midi2freq.pro.user
    ├── main.cpp
    ├── mainwindow.cpp
    ├── mainwindow.h
    ├── mainwindow.ui
    ├── rtaudioutils.cpp
    └── rtaudioutils.h

Author: John Ellinger

Created: 2020-02-21 Fri 11:41

Validate