+static size_t speaker_callback(void *buffer,
+ size_t max_samples,
+ void attribute((unused)) *userdata) {
+ size_t max_bytes = max_samples * uaudio_sample_size;
+ size_t provided_samples = 0;
+
+ /* Be sure to keep the amount of data in a buffer a whole number of frames:
+ * otherwise the playing threads can become stuck. */
+ max_bytes -= max_bytes % (uaudio_sample_size * uaudio_channels);
+
+ pthread_mutex_lock(&lock);
+ /* TODO perhaps we should immediately go silent if we've been asked to pause
+ * or cancel the playing track (maybe block in the cancel case and see what
+ * else turns up?) */
+ if(playing) {
+ if(playing->used > 0) {
+ size_t bytes;
+ /* Compute size of largest contiguous chunk. We get called as often as
+ * necessary so there's no need for cleverness here. */
+ if(playing->start + playing->used > sizeof playing->buffer)
+ bytes = sizeof playing->buffer - playing->start;
+ else
+ bytes = playing->used;
+ /* Limit to what we were asked for */
+ if(bytes > max_bytes)
+ bytes = max_bytes;
+ /* And truncate to a whole number of frames. */
+ bytes -= bytes % (uaudio_sample_size * uaudio_channels);
+ /* Provide it */
+ memcpy(buffer, playing->buffer + playing->start, bytes);
+ playing->start += bytes;
+ playing->used -= bytes;
+ /* Wrap around to start of buffer */
+ if(playing->start == sizeof playing->buffer)
+ playing->start = 0;
+ /* See if we've reached the end of the track; if so make sure the event
+ * loop wakes up. */
+ if(playing->used == 0 && playing->eof) {
+ int ignored = write(sigpipe[1], "", 1);
+ (void) ignored;
+ }
+ provided_samples = bytes / uaudio_sample_size;
+ playing->played += provided_samples;
+ }
+ }
+ /* If we couldn't provide anything at all, play dead air */
+ /* TODO maybe it would be better to block, in some cases? */
+ if(!provided_samples) {
+ memset(buffer, 0, max_bytes);
+ provided_samples = max_samples;
+ if(playing)
+ disorder_info("%zu samples silence, playing->used=%zu",
+ provided_samples, playing->used);
+ else
+ disorder_info("%zu samples silence, playing=NULL", provided_samples);
+ }
+ pthread_mutex_unlock(&lock);
+ return provided_samples;