So here’s my time-stretching code for SDL_Mixer. Maybe this is the
easiest way to explain what I was trying to do
Warning : the following code sucks. It also works. Feel free to
criticize and improve.
Mix_Chunk* Mix_TimeStretchChunk (Mix_Chunk* pChunk, float fFactor)
{
// Get the mixer settings
int nFreq, nChannels;
Uint16 nFormat;
Mix_QuerySpec(&nFreq, &nFormat, &nChannels);
int nBitsPerSample = nFormat & 0x1F;
int nBytesPerSample = nBitsPerSample/8;
bool bSigned = (nFormat & 0x8000) != 0;
bool bLSB = (nFormat & 0x1000) != 0;
GFC_VERIFY((nBitsPerSample % 8) == 0);
// TODO : support other formats
GFC_VERIFY(nBytesPerSample == 2);
GFC_VERIFY(!bLSB);
// Calculate original time, samples and bytes
int nOrigBytes = pChunk->alen;
int nOrigSamples = pChunk->alen / (nBytesPerSample*nChannels);
float fOrigSec = (float)nOrigSamples / (float)nFreq;
// Calculate new time, samples and bytes
float fNewSec = fOrigSec*fFactor;
int nNewSamples = (int)(fNewSec*nFreq);
int nNewBytes = nNewSamples*nBytesPerSample*nChannels;
// Time-stretch
// Take fragments of size RESAMPLE_WINDOW_MS and paste them together,
// cross-fading the overlapping parts
static const int RESAMPLE_WINDOW_MS = 20;
static const int FADE_WINDOW_MS = 5;
short* pDstData = (short*)malloc(nChannels*nNewSamples*sizeof(short));
short* pSrcData = (short*)pChunk->abuf;
int nWindowSamples = (RESAMPLE_WINDOW_MS*nFreq)/1000;
int nFadeSamples = (FADE_WINDOW_MS*nFreq)/1000;
int nShiftSamples = nWindowSamples - nFadeSamples;
int nMidSamples = nWindowSamples - 2*nFadeSamples;
for (int nDstIdx = 0; nDstIdx < nNewSamples; nDstIdx += nShiftSamples)
{
int nSrcIdx = (int)((float)nDstIdx/fFactor);
short* pSrc = &pSrcData[2*nSrcIdx];
short* pDst = &pDstData[2*nDstIdx];
// Make sure we don't go past the end of the buffer
// TODO : find a less stupid way to do this
int nFadeSamplesEnd = nFadeSamples;
int nExtra1 = GFC_MAX(0, nSrcIdx + nWindowSamples - nOrigSamples);
int nExtra2 = GFC_MAX(0, nDstIdx + nWindowSamples - nNewSamples);
int nExtra = GFC_MAX(nExtra1, nExtra2);
if (nExtra > 0)
{
nFadeSamplesEnd -= nExtra;
if (nFadeSamplesEnd < 0)
{
nMidSamples += nFadeSamplesEnd;
nFadeSamplesEnd = 0;
}
if (nMidSamples < 0)
{
nFadeSamples += nMidSamples;
nMidSamples = 0;
}
if (nFadeSamples < 0)
nFadeSamples = 0;
}
// Copy and fade
// TODO : Don't fade in the first sample. 5 ms is barely noticeable
though
float fMul, fDelta;
fMul = 0;
fDelta = 1.0f / nFadeSamples;
for (int j = 0; j < nFadeSamples; j++)
{
*pDst++ += (short)((*pSrc++)*fMul);
*pDst++ += (short)((*pSrc++)*fMul);
fMul += fDelta;
}
for (int j = 0; j < nMidSamples; j++)
{
*pDst++ = *pSrc++;
*pDst++ = *pSrc++;
}
// TODO : Don't fade out the last sample. 5 ms is barely noticeable
though
fMul = 1.0;
fDelta = -1.0f / nFadeSamples;
for (int j = 0; j < nFadeSamplesEnd; j++)
{
*pDst++ = (short)((*pSrc++)*fMul);
*pDst++ = (short)((*pSrc++)*fMul);
fMul += fDelta;
}
}
// Wrap in a chunk
Mix_Chunk* pRet = Mix_QuickLoad_RAW((Uint8*)pDstData, nNewBytes);
if (!pRet)
free(pDstData);
return pRet;
}________________________________________________________________________
Gabriel Gambetta
Mystery Studio - http://www.mysterystudio.com
Gabriel’s Stuff - http://www.mysterystudio.com/gabriel