Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I'm working on an application that plays music.

During playback, often things need to happen on separate threads because they need to happen simultaneously. For example, the notes of a chord need to be heard together, so each one is assigned its own thread to be played in. (Edit to clarify: calling note.play() freezes the thread until the note is done playing, and this is why I need three separate threads to have three notes heard at the same time.)

This kind of behavior creates many threads during playback of a piece of music.

For example, consider a piece of music with a short melody and short accompanying chord progression. The entire melody can be played on a single thread, but the progression needs three threads to play, since each of its chords contains three notes.

So the pseudo-code for playing a progression looks like this:

void playProgression(Progression prog){
    for(Chord chord : prog)
        for(Note note : chord)
            runOnNewThread( func(){ note.play(); } );
}

So assuming the progression has 4 chords, and we play it twice, than we are opening 3 notes * 4 chords * 2 times = 24 threads. And this is just for playing it once.

Actually, it works fine in practice. I don't notice any noticeable latency, or bugs resulting from this.

But I wanted to ask if this is correct practice, or if I'm doing something fundamentally wrong. Is it reasonable to create so many threads each time the user pushes a button? If not, how can I do it differently?

share|improve this question
14  
Perhaps you should consider mixing the audio instead? I don't know what framework you're using but here's an example: wiki.libsdl.org/SDL_MixAudioFormat Or, you could make use of channels: libsdl.org/projects/SDL_mixer/docs/SDL_mixer_25.html#SEC25 –  Rufflewind Aug 26 at 0:48
5  
Is it reasonable to create so many threads... depends on the threading model of the language. Threads used for parallelism are often handled at the OS level so the OS can map them to multiple cores. Such threads are expensive to create and switch between. Threads for concurrency (interleaving two tasks, not necessarily executing both simultaneously) can be implemented at the language/VM level and can be made extremely "cheap" to produce and switch between so you can, say, talk to 10 network sockets more or less simultaneously, but you won't necessarily get more CPU throughput that way. –  Doval Aug 26 at 11:48
3  
That aside, the others are definitely right about threads being the wrong way to handle playing multiple sounds at once. –  Doval Aug 26 at 11:49
3  
Do you have much familiarity with how sound waves work? Typically you make a chord by composing the values of two sound waves (specified at the same bitrate) together into a new sound wave. Complex waves can be built up from simple ones; you only ever require a single waveform to play a song. –  KChaloux Aug 26 at 12:10
    
Since you say that note.play() is not asynchronous, a thread for each note.play() is therefore an approach to playing multiple notes simultaneously. UNLESS.., you are able to combine those notes into one which you then play on a single thread. If that is not possible then with your approach you are going to have to use some mechanism to make sure they remain in sync –  pnizzle Aug 26 at 23:25

6 Answers 6

up vote 46 down vote accepted

One assumption you are making might not be valid: you require (among other things) that your threads execute simultaneously. Might work for 3, but at some point the system is going to need to prioritize which threads to run first, and which one wait.

Your implementation will ultimately depend on your API, but most modern APIs will let you tell in advance what you want to play and take care of the timing and queuing themselves. If you were to code such an API yourself, ignoring any existing system API (why would you?!), an event queue mixing your notes and playing them from a single thread looks like a better approach than a thread per note model.

share|improve this answer
35  
Or to state it differently -- the system will not and cannot guarantee the order, sequence or duration of any thread once started. –  James Anderson Aug 26 at 1:08
2  
@JamesAnderson except if one would undertake massive efforts in synchronization, which would end up at being almost squetially in the end again. –  Mark Aug 26 at 6:35
    
By 'API' you mean the audio library I'm using? –  Aviv Cohn Aug 26 at 21:21
1  
@Prog Yes. I'm pretty sure it has something more convenient that note.play() –  ptyx Aug 26 at 21:38

Do not rely on threads executing in lockstep. Every operating system I know of does not make the guarantee that threads execute in time consistent with each other. This means that if the CPU runs 10 threads, they do not necessarily receive equal time in any given second. They could quickly get out of synch, or they could stay perfectly in synch. That is the nature of threads: nothing is guaranteed, since the behavior of their execution is non-deterministic.

For this specific application, I believe you need to have a single thread that consumes music notes. Before it can consume the notes, some other process needs to merge instruments, staffs, whatever into a single piece of music.

Let us assume that you have e.g. three threads producing notes. I would synchronize them on a single data structure where they can all put their music. Another thread (consumer) reads the combined notes and plays them. There may need to be a short delay here to ensure that thread timing does not cause notes to go missing.

Related reading: producer-consumer problem.

share|improve this answer
1  
Thanks for answering. I'm not 100% sure I understand you're answer, but I wanted to make sure you understand why I need 3 threads to play 3 notes at the same time: it's because calling note.play() freezes the thread until the note is done playing. So in order for me to be able to play() 3 notes at the same time, I need 3 different threads to do this. Does your solution address my situation? –  Aviv Cohn Aug 26 at 21:25
    
No, and that was not clear from the question. Is there no way for a thread to play a chord? –  Snowman Aug 26 at 21:29

A classic approach for this music problem would be to use exactly two threads. One, a lower priority thread would handle the UI or sound generating code like

void playProgression(Progression prog){
    for(Chord chord : prog)
        for(Note note : chord)
            otherthread.startPlaying(note);
}

(note the concept of only starting the note asynchronously and going on without waiting for it to finish)

and the second, realtime thread would consistently look at all the notes, sounds, samples, etc that should be playing now; mix them and output the final waveform. This part may be (and often is) taken from a polished third party library.

That thread would often be very sensitive to "starvation" of resources - if any actual sound output processing is blocked for longer than your sound card buffered output, then it will cause audible artifacts such as interruptions or pops. If you have 24 threads that are directly outputing audio, then you have a much higher chance that one of them will stutter at some point. This is often considered unacceptable, as humans are rather sensitive to audio glitches (much more than to visual artifacts) and notice even minor stuttering.

share|improve this answer
1  
OP says API can only play one note at a time –  Mooing Duck Aug 26 at 23:04
4  
@MooingDuck if API can mix, then it should mix; if the OP say's that the API can't mix then the solution is to mix in your code and have that other thread perform my_mixed_notes_from_whole_chord_progression.play() through the api. –  Peteris Aug 26 at 23:10

Well, yes, you are doing something wrong.

The first thing is that creating Threads is expensive. It has much more overhead than just calling a function.

So what you should do, if you need multiple threads for this job is to recycle the threads.

As it looks to me you have one main thread that does the scheduling of the other threads. So the main thread leads through the music and starts new threads whenever there is a new note to be played. So instead of letting the threads die and restarting them, better keep the other threads alive in a sleep loop, where they check every x milliseconds (or nanoseconds) if there is a new note to be played and otherwise sleep. The main thread then doesn't start new threads but tells the existing thread pool to play notes. Only if there aren't enough threads in the pool it can create new threads.

The next one is the synchronization. Hardly any modern multithreading systems actually guarantee that all threads are executed at the same time. If you have more threads and processes running on the machine than cores (which is mostly the case), then the threads don't get 100% of the CPU time. They have to share the CPU. This means every thread gets a small amount of CPU time and then after it had it's share the next thread gets the CPU for a short time. The system doesn't guarantee that your thread gets the same CPU time as the other threads. This means, one thread might be waiting for another one to finish, and thus be delayed.

You should rather have a look if it isn't possible to play multiple notes on one thread, so that the thread prepares all the notes and then only gives the "start" command.

If you need to do it with threads, at least reuse the threads. Then you don't need to have as many threads as there are notes in the whole piece, but only need as many threads as the maximum number of notes played at the same time.

share|improve this answer
    
"[Is it] possible to play multiple notes on one thread, so that the thread prepares all the notes and then only gives the "start" command." - This was my first thought as well. I sometimes wonder if being bombarded with commentary about multithreading (e.g. programmers.stackexchange.com/questions/43321/…) doesn't lead a lot of programmers astray during design. I'm skeptical of any big, up-front process that ends up positing the need for a bunch of threads. I'd advise looking intently for a single-thread solution. –  user1172763 Aug 26 at 16:24

The pragmatic answer is that if it works for your application and meets your current requirements then you're not doing it wrong :) However if you mean "is this a scalable, efficient, reusable solution" then the answer is no. The design makes many assumptions about how the threads will behave which may or may not be true in different situations (longer melodies, more simultaneous notes, different hardware, etc.).

As an alternative consider using a timing loop and a thread pool. The timing loop runs in it's own thread and continually checks if a note needs to be played. It does this by comparing the system time against the time that the melody was started. The timing of each note can normally be calculated very easily from the tempo of the melody and the sequence of notes. As a new note needs to be played, borrow a thread from a thread pool and call the play() function on the note. The timing loop can work in various ways but the simplest is a continuous loop with short sleeps (probably between chords) to minimise CPU usage.

An advantage of this design is that the number of threads will not exceed be the maximum number of simultaneous notes + 1 (the timing loop). Also the timing loop protects you against slippages in timings which could be caused by thread latency. Thirdly the tempo of the melody is not fixed and can be changed by altering the calculation of the timings.

As an aside, I agree with other commenters that the function note.play() is a very poor API to work with. Any reasonable sound API would allow you to mix and schedule notes in a much more flexible way. That said, sometimes we have to live with what we've got :)

share|improve this answer

Looks fine to me as a simple implementation, assuming that is the API you have to use. Other answers cover why this isn't a very good API, so I'm not talking about that more, I just assume it is what you have to live with. Your approach will use a large number of threads, but on a modern PC that should be no concern, as long as thread count is in the dozens.

One thing you should do, if feasible (like, playing from a file instead of from user hitting the keyboard), is to add some latency. So you start a thread, it sleeps until specific time of system clock, and starts to play a note at the right time (note, sometimes sleep may be interrupted early, so check clock and sleep more if needed). While there is no guarantee that OS will continue the thread exactly when your sleep ends (unless you use a real-time operating system), it's very likely to be much more accurate than if you just start the thread and start playing without checking the timing.

Then next step, which complicates things a bit but not too much, and will allow you to reduce the above mentioned latency is, use a thread pool. That is, when a thread finishes a note, it doesn't exit, but instead waits for a new note to play. And when you request starting to play a note, you first try to take a free thread from the pool, and add a new thread only if needed. This will require some simple inter-thread communication, of course, instead of your current fire-and-forget approach.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.