Skip to content

Instantly share code, notes, and snippets.

@derselbst
Last active February 21, 2016 08:58
Show Gist options
  • Save derselbst/f2d624faf210797f522f to your computer and use it in GitHub Desktop.
Save derselbst/f2d624faf210797f522f to your computer and use it in GitHub Desktop.
Looped Audio Playback
/*
** 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