I'm pretty new to C++, but have some experience with Python. I wanted to make a program that played certain frequencies based on different conditions, but I soon realized I couldn't even figure out how to play an audio file. From what I found looking for a solution, it seems almost impossible on Mac, and everything I tried copying into Xcode ended up with a bunch of errors. Where should I start with this?
The problem here is that C++ is just a programming language. The same can be said for python, though Python lives in a different ecosystem of modules and package management which get conflated (rightly or wrongly) as part of the language
C++ doesn’t have the same history and the same ecosystem and this is part of the battle you will have when learning it. You don’t have pip
, you have a nebulous series of frameworks, headers and libraries (some standard, some which need installation) all of which need linked, path-ed or compiled. It is an ecosystem that is unfriendly if you try and approach it like a novice Python programmer. If you approach it agnostically, it is simultaneously very powerful and exceptionally tedious, a combination that tends to polarise developers.
This means that simple answers like Use SFML!, SDL, NSOUND, OpenAL, CoreAudio, AVFoundation, JUCE, &c... are all technically "correct" but massively gloss over large parts of setup, nomenclature and workflow that are just a pip install
away with python.
Pontificating aside, if you want to simply
Then you are probably best just
.wav
.wav
with afplay
Is that the most open, versatile, DSP orientated, play-from-RAM solution? No, of course not, but it is a solution to the problem you pose here. The alternative and correct answer is an exhaustive list of every major media library, cross-platform and macOS specific, their setup, quirks and minimum working example, which would result in an answer so obtusely long I hope you can sympathise with why it is not best addressed on Stack Overflow.
You can find all the constituent parts of this on SO, but I have tallied-off so many how do I play a sound in C++ questions it has made me realise they are not going away.
The setup for Xcode is to create a Command Line Tool project (Console App for Visual Studio).
Here is a header that will wrap up everything into a playSound
function
#pragma once
//------------------------------------------------------------------------------
#include <iostream>
#include <fstream>
#include <cstddef>
#include <cstdlib>
#if defined _WIN32 || defined _WIN64
#pragma comment(lib, "Winmm")
#include <windows.h>
#endif
//------------------------------------------------------------------------------
/// <#Description#>
struct WaveHeader
{
/** waveFormatHeader: The first 4 bytes of a wav file should be the characters "RIFF" */
char chunkID[4] = { 'R', 'I', 'F', 'F' };
/** waveFormatHeader: This is the size of the entire file in bytes minus 8 bytes */
uint32_t chunkSize;
/** waveFormatHeader" The should be characters "WAVE" */
char format[4] = { 'W', 'A', 'V', 'E' };
/** waveFormatHeader" This should be the letters "fmt ", note the space character */
char subChunk1ID[4] = { 'f', 'm', 't', ' ' };
/** waveFormatHeader: For PCM == 16, since audioFormat == uint16_t */
uint32_t subChunk1Size = 16;
/** waveFormatHeader: For PCM this is 1, other values indicate compression */
uint16_t audioFormat = 1;
/** waveFormatHeader: Mono = 1, Stereo = 2, etc. */
uint16_t numChannels = 1;
/** waveFormatHeader: Sample Rate of file */
uint32_t sampleRate = 44100;
/** waveFormatHeader: SampleRate * NumChannels * BitsPerSample/8 */
uint32_t byteRate = 44100 * 2;
/** waveFormatHeader: The number of bytes for one sample including all channels */
uint16_t blockAlign = 2;
/** waveFormatHeader: 8 bits = 8, 16 bits = 16 */
uint16_t bitsPerSample = 16;
/** waveFormatHeader: Contains the letters "data" */
char subChunk2ID[4] = { 'd', 'a', 't', 'a' };
/** waveFormatHeader: == NumberOfFrames * NumChannels * BitsPerSample/8
This is the number of bytes in the data.
*/
uint32_t subChunk2Size;
WaveHeader(uint32_t samplingFrequency = 44100, uint16_t bitDepth = 16, uint16_t numberOfChannels = 1)
{
numChannels = numberOfChannels;
sampleRate = samplingFrequency;
bitsPerSample = bitDepth;
byteRate = sampleRate * numChannels * bitsPerSample / 8;
blockAlign = numChannels * bitsPerSample / 8;
};
/// sets the fields that refer to how large the wave file is
/// @warning This MUST be set before writing a file, or the file will be unplayable.
/// @param numberOfFrames total number of audio frames. i.e. total number of samples / number of channels
void setFileSize(uint32_t numberOfFrames)
{
subChunk2Size = numberOfFrames * numChannels * bitsPerSample / 8;
chunkSize = 36 + subChunk2Size;
}
};
/// write an array of float data to a 16-bit, 44100 Hz Mono wav file in the same directory as the program and then play it
/// @param audio audio samples, assumed to be 44100 Hz sampling rate
/// @param numberOfSamples total number of samples in audio
/// @param filename filename, should end in .wav and will be written to your Desktop
void playSound(float* audio,
uint32_t numberOfSamples,
const char* filename)
{
std::ofstream fs;
std::string filepath {filename};
if (filepath.substr(filepath.size() - 4, 4) != std::string(".wav"))
filepath += std::string(".wav");
fs.open(filepath, std::fstream::out | std::ios::binary);
WaveHeader* header = new WaveHeader{};
header->setFileSize(numberOfSamples);
fs.write((char*)header, sizeof(WaveHeader));
int16_t* audioData = new int16_t[numberOfSamples];
constexpr float max16BitValue = 32768.0f;
for (int i = 0; i < numberOfSamples; ++i)
{
int pcm = int(audio[i] * (max16BitValue));
if (pcm >= max16BitValue)
pcm = max16BitValue - 1;
else if (pcm < -max16BitValue)
pcm = -max16BitValue;
audioData[i] = int16_t(pcm);
}
fs.write((char*)audioData, header->subChunk2Size);
fs.close();
std::cout << filename << " written to:\n" << filepath << std::endl;
#if defined _WIN32 || defined _WIN64
// don't forget to add Add 'Winmm.lib' in Properties > Linker > Input > Additional Dependencies
PlaySound(std::wstring(filepath.begin(), filepath.end()).c_str(), NULL, SND_FILENAME);
#else
std::system((std::string("afplay ") + filepath).c_str());
#endif
}
Your main
function could then be something like:
#include <iostream>
#include <cmath>
#include "audio.h"
int main(int argc, const char * argv[])
{
const int numSamples = 44100;
float sampleRate = 44100.0f;
float* sineWave = new float[numSamples];
float frequency = 440.0f;
float radsPerSamp = 2.0f * 3.1415926536f * frequency / sampleRate;
for (unsigned long i = 0; i < numSamples; i++)
{
sineWave[i] = std::sin (radsPerSamp * (float) i);
}
playSound(sineWave, numSamples, "test.wav");
return 0;
}