skip to Main Content

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?

2

Answers


  1. I once used SFML for this. Although I did it on Linux, I see that its available for mac as well. You can go through the tutorials here.

    Login or Signup to reply.
  2. 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

    • create an array of floating point values
    • that represent the samples of sine tone
    • then play those samples
    • on macOS

    Then you are probably best just

    • creating your array
    • writing a .wav
    • opening the .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.

    A Simple CLI App

    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

    audio.h

    #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
        
    }
    
    

    main.cpp

    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;
    }
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search