Last active
February 21, 2016 08:58
-
-
Save derselbst/f2d624faf210797f522f to your computer and use it in GitHub Desktop.
Looped Audio Playback
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
** Copyright (C) derselbst (Tom M.) | |
** | |
** All rights reserved. | |
** | |
** Redistribution and use in source and binary forms, with or without | |
** modification, are permitted provided that the following conditions are | |
** met: | |
** | |
** * Redistributions of source code must retain the above copyright | |
** notice, this list of conditions and the following disclaimer. | |
** * Redistributions in binary form must reproduce the above copyright | |
** notice, this list of conditions and the following disclaimer in | |
** the documentation and/or other materials provided with the | |
** distribution. | |
** * Neither the author nor the names of any contributors may be used | |
** to endorse or promote products derived from this software without | |
** specific prior written permission. | |
** | |
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED | |
** TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
** PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |
** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; | |
** OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | |
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR | |
** OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |
** ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
// Build: g++ sndfile-loop-play.c -lasound -lsndfile -o sndplay | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <errno.h> | |
#if HAVE_UNISTD_H | |
#include <unistd.h> | |
#endif | |
#include <sndfile.h> | |
// needs alsa-devel to be installed | |
#define HAVE_ALSA_ASOUNDLIB_H 1 | |
#if HAVE_ALSA_ASOUNDLIB_H | |
#define ALSA_PCM_NEW_HW_PARAMS_API | |
#define ALSA_PCM_NEW_SW_PARAMS_API | |
#include <alsa/asoundlib.h> | |
#include <sys/time.h> | |
#endif | |
#if defined (__linux__) | |
#include <fcntl.h> | |
#include <sys/ioctl.h> | |
#include <sys/soundcard.h> | |
#endif | |
#include <time.h> | |
unsigned int globalLoopCount=-1; | |
#if HAVE_ALSA_ASOUNDLIB_H | |
static snd_pcm_t * alsa_open (int channels, unsigned srate, int realtime) ; | |
static int alsa_write_float (snd_pcm_t *alsa_dev, const float *data, const int frames, const int channels) ; | |
static snd_pcm_t * | |
alsa_open (int channels, unsigned samplerate, int realtime) | |
{ const char * device = "default" ; | |
snd_pcm_t *alsa_dev = NULL ; | |
snd_pcm_hw_params_t *hw_params ; | |
snd_pcm_uframes_t buffer_size ; | |
snd_pcm_uframes_t alsa_period_size, alsa_buffer_frames ; | |
snd_pcm_sw_params_t *sw_params ; | |
int err ; | |
if (realtime) | |
{ alsa_period_size = 256 ; | |
alsa_buffer_frames = 3 * alsa_period_size ; | |
} | |
else | |
{ alsa_period_size = 1024 ; | |
alsa_buffer_frames = 4 * alsa_period_size ; | |
} ; | |
if ((err = snd_pcm_open (&alsa_dev, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) | |
{ fprintf (stderr, "cannot open audio device \"%s\" (%s)\n", device, snd_strerror (err)) ; | |
goto catch_error ; | |
} ; | |
snd_pcm_nonblock (alsa_dev, 0) ; | |
if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) | |
{ fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n", snd_strerror (err)) ; | |
goto catch_error ; | |
} ; | |
if ((err = snd_pcm_hw_params_any (alsa_dev, hw_params)) < 0) | |
{ fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n", snd_strerror (err)) ; | |
goto catch_error ; | |
} ; | |
if ((err = snd_pcm_hw_params_set_access (alsa_dev, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) | |
{ fprintf (stderr, "cannot set access type (%s)\n", snd_strerror (err)) ; | |
goto catch_error ; | |
} ; | |
if ((err = snd_pcm_hw_params_set_format (alsa_dev, hw_params, SND_PCM_FORMAT_FLOAT)) < 0) | |
{ fprintf (stderr, "cannot set sample format (%s)\n", snd_strerror (err)) ; | |
goto catch_error ; | |
} ; | |
if ((err = snd_pcm_hw_params_set_rate_near (alsa_dev, hw_params, &samplerate, 0)) < 0) | |
{ fprintf (stderr, "cannot set sample rate (%s)\n", snd_strerror (err)) ; | |
goto catch_error ; | |
} ; | |
if ((err = snd_pcm_hw_params_set_channels (alsa_dev, hw_params, channels)) < 0) | |
{ fprintf (stderr, "cannot set channel count (%s)\n", snd_strerror (err)) ; | |
goto catch_error ; | |
} ; | |
if ((err = snd_pcm_hw_params_set_buffer_size_near (alsa_dev, hw_params, &alsa_buffer_frames)) < 0) | |
{ fprintf (stderr, "cannot set buffer size (%s)\n", snd_strerror (err)) ; | |
goto catch_error ; | |
} ; | |
if ((err = snd_pcm_hw_params_set_period_size_near (alsa_dev, hw_params, &alsa_period_size, 0)) < 0) | |
{ fprintf (stderr, "cannot set period size (%s)\n", snd_strerror (err)) ; | |
goto catch_error ; | |
} ; | |
if ((err = snd_pcm_hw_params (alsa_dev, hw_params)) < 0) | |
{ fprintf (stderr, "cannot set parameters (%s)\n", snd_strerror (err)) ; | |
goto catch_error ; | |
} ; | |
/* extra check: if we have only one period, this code won't work */ | |
snd_pcm_hw_params_get_period_size (hw_params, &alsa_period_size, 0) ; | |
snd_pcm_hw_params_get_buffer_size (hw_params, &buffer_size) ; | |
if (alsa_period_size == buffer_size) | |
{ fprintf (stderr, "Can't use period equal to buffer size (%lu == %lu)", alsa_period_size, buffer_size) ; | |
goto catch_error ; | |
} ; | |
snd_pcm_hw_params_free (hw_params) ; | |
if ((err = snd_pcm_sw_params_malloc (&sw_params)) != 0) | |
{ fprintf (stderr, "%s: snd_pcm_sw_params_malloc: %s", __func__, snd_strerror (err)) ; | |
goto catch_error ; | |
} ; | |
if ((err = snd_pcm_sw_params_current (alsa_dev, sw_params)) != 0) | |
{ fprintf (stderr, "%s: snd_pcm_sw_params_current: %s", __func__, snd_strerror (err)) ; | |
goto catch_error ; | |
} ; | |
/* note: set start threshold to delay start until the ring buffer is full */ | |
snd_pcm_sw_params_current (alsa_dev, sw_params) ; | |
if ((err = snd_pcm_sw_params_set_start_threshold (alsa_dev, sw_params, buffer_size)) < 0) | |
{ fprintf (stderr, "cannot set start threshold (%s)\n", snd_strerror (err)) ; | |
goto catch_error ; | |
} ; | |
if ((err = snd_pcm_sw_params (alsa_dev, sw_params)) != 0) | |
{ fprintf (stderr, "%s: snd_pcm_sw_params: %s", __func__, snd_strerror (err)) ; | |
goto catch_error ; | |
} ; | |
snd_pcm_sw_params_free (sw_params) ; | |
snd_pcm_reset (alsa_dev) ; | |
catch_error : | |
if (err < 0 && alsa_dev != NULL) | |
{ snd_pcm_close (alsa_dev) ; | |
return NULL ; | |
} ; | |
return alsa_dev ; | |
} /* alsa_open */ | |
static int | |
alsa_write_float (snd_pcm_t *alsa_dev, const float *data, const int frames, const int channels) | |
{ static int epipe_count = 0 ; | |
int total = 0 ; | |
int retval ; | |
if (epipe_count > 0) | |
epipe_count -- ; | |
while (total < frames) | |
{ retval = snd_pcm_writei (alsa_dev, data + total * channels, frames - total) ; | |
if (retval >= 0) | |
{ total += retval ; | |
if (total == frames) | |
return total ; | |
continue ; | |
} ; | |
switch (retval) | |
{ | |
case -EAGAIN : | |
puts ("alsa_write_float: EAGAIN") ; | |
continue ; | |
break ; | |
case -EPIPE : | |
if (epipe_count > 0) | |
{ printf ("alsa_write_float: EPIPE %d\n", epipe_count) ; | |
if (epipe_count > 140) | |
return retval ; | |
} ; | |
epipe_count += 100 ; | |
#if 0 | |
if (0) | |
{ snd_pcm_status_t *status ; | |
snd_pcm_status_alloca (&status) ; | |
if ((retval = snd_pcm_status (alsa_dev, status)) < 0) | |
fprintf (stderr, "alsa_out: xrun. can't determine length\n") ; | |
else if (snd_pcm_status_get_state (status) == SND_PCM_STATE_XRUN) | |
{ struct timeval now, diff, tstamp ; | |
gettimeofday (&now, 0) ; | |
snd_pcm_status_get_trigger_tstamp (status, &tstamp) ; | |
timersub (&now, &tstamp, &diff) ; | |
fprintf (stderr, "alsa_write_float xrun: of at least %.3f msecs. resetting stream\n", | |
diff.tv_sec * 1000 + diff.tv_usec / 1000.0) ; | |
} | |
else | |
fprintf (stderr, "alsa_write_float: xrun. can't determine length\n") ; | |
} ; | |
#endif | |
snd_pcm_prepare (alsa_dev) ; | |
break ; | |
case -EBADFD : | |
fprintf (stderr, "alsa_write_float: Bad PCM state.n") ; | |
return 0 ; | |
break ; | |
case -ESTRPIPE : | |
fprintf (stderr, "alsa_write_float: Suspend event.n") ; | |
return 0 ; | |
break ; | |
case -EIO : | |
puts ("alsa_write_float: EIO") ; | |
return 0 ; | |
default : | |
fprintf (stderr, "alsa_write_float: retval = %d\n", retval) ; | |
return 0 ; | |
break ; | |
} ; /* switch */ | |
} ; /* while */ | |
return total ; | |
} /* alsa_write_float */ | |
#endif /* HAVE_ALSA_ASOUNDLIB_H */ | |
snd_pcm_t * alsa_dev ; | |
void playSamples(float* buffer, const int floatsToPlay, const int& channels) | |
{ | |
// for(int i=0; i<floatsToPlay; i+=channels) | |
// alsa_write_float (alsa_dev, buffer+i, 1, channels) ; | |
// | |
const int FRAMES = 2048; | |
const int BUFSIZE = FRAMES * channels; | |
int fullTransfers = floatsToPlay / BUFSIZE; | |
for(int i=0; i<fullTransfers; i++) | |
{ | |
alsa_write_float (alsa_dev, buffer+(i*BUFSIZE), FRAMES /* or more correctly BUFSIZE/channels , its the same*/, channels); | |
} | |
int finalTransfer = floatsToPlay % BUFSIZE; | |
alsa_write_float (alsa_dev, buffer+(fullTransfers*BUFSIZE), finalTransfer/channels, channels); | |
} | |
SF_INSTRUMENT inst; | |
void playLoop(float* buffer, const int& channels, const int loopIdx) | |
{ | |
unsigned int loopCount = globalLoopCount!=-1 ? globalLoopCount : inst.loops[loopIdx].count; | |
unsigned int startIdx = inst.loops[loopIdx].start * channels; | |
unsigned int endIdx = inst.loops[loopIdx].end * channels; | |
// exclude last sample from being repeated | |
endIdx-= channels; | |
// if(loopIdx+1<inst.loop_count) | |
// { | |
// unsigned int& startNext = inst.loops[loopIdx+1].start; | |
// unsigned int& endNext = inst.loops[loopIdx+1].endIdx; | |
// | |
// bool forever = loopCount==0; | |
// while(forever || loopCount--) | |
// { | |
// for(int i=startIdx; i<endIdx-channels; i+=channels) | |
// { | |
// if(startNext==i) | |
// { | |
// playLoop(buffer, startNext, endNext-startNext, channels, loopIdx+1); | |
// | |
// // actually we just want to continue playing at endNext, but for what ever reason | |
// // it could be that this isnt part of our sample buffer anymore. so recheck our | |
// // loop condition by continuing. but before continuing decrease i by one sample | |
// // since it will be increased by one when reentering the loop | |
// i=endNext-channels; | |
// continue; | |
// } | |
// playSamples(buffer+i, 1, channels); | |
// | |
// } | |
// } | |
// } | |
// else | |
{ | |
bool forever = loopCount==0; | |
while(forever || loopCount--) | |
{ | |
playSamples(buffer+startIdx, endIdx-startIdx, channels); | |
} | |
playSamples(buffer+endIdx, 2, channels); | |
// playSamples(buffer+endIdx+channels, 1, channels); | |
} | |
} | |
/*============================================================================== | |
** Main function. | |
*/ | |
int | |
main (int argc, char *argv []) | |
{ | |
if (argc < 2) | |
{ | |
printf ("\nUsage : %s L <input sound file>\n\n", argv [0]); | |
printf ("Using %s.\n\n", sf_version_string ()) ; | |
return 1 ; | |
} ; | |
if(atoi(argv[1])!=-1) | |
globalLoopCount=atoi(argv[1]); | |
#if HAVE_ALSA_ASOUNDLIB_H | |
if (access ("/proc/asound/cards", R_OK) == 0) | |
{ | |
//alsa_play (argc, argv) ; | |
SNDFILE *sndfile ; | |
SF_INFO sfinfo ; | |
clock_t t; | |
double time_taken; | |
for (int k = 2 ; k < argc ; k++) | |
{ | |
t = clock(); | |
memset (&sfinfo, 0, sizeof (sfinfo)) ; | |
printf ("Playing %s\n", argv [k]) ; | |
if (! (sndfile = sf_open (argv [k], SFM_READ, &sfinfo))) | |
{ puts (sf_strerror (NULL)) ; | |
continue ; | |
} ; | |
if (sfinfo.channels < 1 || sfinfo.channels > 2) | |
{ printf ("Error : channels = %d.\n", sfinfo.channels) ; | |
continue ; | |
} ; | |
if ((alsa_dev = alsa_open (sfinfo.channels, (unsigned) sfinfo.samplerate, SF_FALSE)) == NULL) | |
continue; | |
t = clock() - t; | |
time_taken = ((double)t)/CLOCKS_PER_SEC; // in seconds | |
printf("init() took %f seconds to execute \n", time_taken); | |
t = clock(); | |
size_t BUFFER_LEN = sfinfo.frames * sfinfo.channels; | |
float* buffer = new float[BUFFER_LEN]; | |
t = clock() - t; | |
time_taken = ((double)t)/CLOCKS_PER_SEC; // in seconds | |
printf("alloc() took %f seconds to execute \n", time_taken); | |
t = clock(); | |
int readcount = sf_read_float (sndfile, buffer, BUFFER_LEN); | |
t = clock() - t; | |
time_taken = ((double)t)/CLOCKS_PER_SEC; // in seconds | |
printf("sf_read_float() took %f seconds to execute \n", time_taken); | |
if(readcount != BUFFER_LEN) | |
printf("THIS SHOULD NEVER HAPPEN: only read %d frames, although there are %d frames in the file\n", readcount/sfinfo.channels, sfinfo.frames); | |
int subformat = sfinfo.format & SF_FORMAT_SUBMASK ; | |
if (subformat == SF_FORMAT_FLOAT || subformat == SF_FORMAT_DOUBLE) | |
{ | |
double scale; | |
sf_command (sndfile, SFC_CALC_SIGNAL_MAX, &scale, sizeof (scale)); | |
if (scale < 1e-10) | |
{ | |
scale = 1.0; | |
} | |
else | |
{ | |
scale = 32700.0 / scale; | |
} | |
puts(":D"); | |
for (int m = 0 ; m < readcount; m++) | |
buffer [m] *= scale; | |
} | |
int ret = sf_command (sndfile, SFC_GET_INSTRUMENT, &inst, sizeof (inst)) ; | |
if(ret == SF_TRUE && inst.loop_count > 0) | |
{ | |
playSamples(buffer, inst.loops[0].start * sfinfo.channels, sfinfo.channels); | |
playLoop(buffer, sfinfo.channels, 0); | |
playSamples(buffer + (inst.loops[0].end) * sfinfo.channels, readcount - inst.loops[0].end * sfinfo.channels, sfinfo.channels); | |
} | |
else | |
playSamples(buffer, readcount, sfinfo.channels); | |
t = clock(); | |
delete [] buffer; | |
snd_pcm_drain (alsa_dev) ; | |
snd_pcm_close (alsa_dev) ; | |
sf_close (sndfile); | |
t = clock() - t; | |
time_taken = ((double)t)/CLOCKS_PER_SEC; // in seconds | |
printf("cleanup() took %f seconds to execute \n", time_taken); | |
} | |
} | |
#endif | |
return 0 ; | |
} /* main */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment