In university I took a course on standard C++11 multithreading. One of the main projects for that course was this audio player that has a collection of artificial constraints that exercises the idea of contention and synchonization. The audio player is Microsoft's archaic WaveOut API to play the chunks of raw WAVE files.
Kill ThreadOne of the main threads (apart from the main itself) is the Kill thread, whose job is to coordinate the termination of all of the other created threads. Since C++ has no exact way of terminating threads directly, this thread uses std::promise and its std::future shared between all of the created threads to signal whether they should be stopped. A key press can be used to stop the program using this functionality. | ![]() |
![]() | File ThreadThe File thread is responsible for loading individual chunks of audio whenever it is necessary. Over the course of the program, the Coordinator will transfer (copy) the File thread's entire buffer, and that would be the cue for the File thread to load the next file. As part of the program's artificial problem, the file reading is significantly slowed down. |
Coordinator ThreadThe Coordinator thread copies data from the File thread into one of its two A/B buffers and is responsible for sending 2K chunks of data to the various WaveBuffer objects. Throughout the course of the program, the Coordinator will cycle between loading data from File into the designated “empty” A or B buffer, sending 2K chunks from the designated “playback” buffer through commands enqueued by Playback, and swapping between its two A/B buffers when the “playback” buffer has reached its end. | ![]() |
![]() | Playback ThreadThe Playback thread cycles between supplying the Coordinator's queue of commands and playing back the WaveBuffer object buffers in the right order. The class also contains the necessary data for handling the WaveOut API and is responsible for creating the WaveBuffer objects. As part of handling WaveOut, Playback contains the callback function that WaveOut calls after a 2K chunk of data is finished playing. There, the WaveBuffer is enqueued into Playback's queue responsible for sending commands to the Coordinator thread. To keep track of the WaveBuffer objects that are ready to be played, my system incorporates a separate playback queue. This new queue is pushed to by the commands executed by Coordinator when supplying the WaveBuffer threads with data. |