aboutsummaryrefslogblamecommitdiffstats
path: root/src/pcm_utils.c
blob: bfb11e3ffd6aa7323e4d01b230e3392002409d70 (plain) (tree)


















                                                                            




                      
                   

                   
                   
                 

                                                                          

                                     


                                   
                                            




                                              












                                                                           





                                                                          























                                                                                

                                                     


                                                     
 
                                





                                            
























                                                                                  
                              






                                                                      
                


                                                           



                                      


                                                          





                                                                     
                                   
         
      


                                                                 
                                                                             
 
































                                                                                



                                                        




















                                                                                
                              












                                                                                
                




                                                                           




                                       
                                                                          

                      




                                                                         




                                      
                                                                        


                                                                          
                                   
         
      




                                                                         
                 

                                       
        

                                                        
 
                                                                               
 
 

                                                                        
 





                                                   

                                


                                                       


                      



                                                        
                      





                                                        
                

                                                                           

                                   





















































                                                                                    
 







































                                                                                    
         



























































































































                                                                                      

              







                                                                          


                                                  
                         





                                                                         



                                                  
                         





                                                          





                                                                                      
                                      

         




                                                              
         
 




                                                                     
         






                                                                          
               












                                                                

                      












                                                                 
                      














                                                                            
                
                                                                                      

                                   
 










                                                                             
         








                                                                              
        
                       
 
/* the Music Player Daemon (MPD)
 * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu)
 * This project's homepage is: http://www.musicpd.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#include "pcm_utils.h"

#include "mpd_types.h"
#include "log.h"

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include <time.h>

void pcm_changeBufferEndianness(char * buffer, int bufferSize, int bits) {
	
ERROR("pcm_changeBufferEndianess\n");
	switch(bits) {
	case 16:
		while(bufferSize) {
			char temp = *buffer;
			*buffer = *(buffer+1);
			*(buffer+1) = temp;
			bufferSize-=2;
		}
		break;
	case 32:
		/* I'm not sure if this code is correct */
		/* I guess it is OK for 32 bit int, but how about float? */
		while(bufferSize) {
			char temp = *buffer;
			char temp1 = *(buffer+1);
			*buffer = *(buffer+3);
			*(buffer+1) = *(buffer+2);
			*(buffer+2) = temp1;
			*(buffer+3) = temp;
			bufferSize-=4;
		}
		break;
	}
}

void pcm_volumeChange(char * buffer, int bufferSize, AudioFormat * format,
		int volume)
{
#ifdef MPD_FIXED_POINT
	mpd_sint16 * buffer16 = (mpd_sint16 *)buffer;
	mpd_sint32 sample32;

	if(volume>=1000) return;
	
	if(volume<=0) {
		memset(buffer,0,bufferSize);
		return;
	}

	if(format->bits!=16 || format.channels!=2 || format->floatSamples) {
		ERROR("Only 16 bit stereo is supported in fixed point mode!\n");
		exit(EXIT_FAILURE);
	}

	while(bufferSize) {
		sample32 = (mpd_sint32)(*buffer16) * volume;
		/* no need to check boundaries - can't overflow */
		*buffer16 = sample32 >> 10;
		bufferSize -= 2;
	}
	return;
#else
	mpd_sint8 * buffer8 = (mpd_sint8 *)buffer;
	mpd_sint16 * buffer16 = (mpd_sint16 *)buffer;
	mpd_sint32 * buffer32 = (mpd_sint32 *)buffer;
	float * bufferFloat = (float *)buffer;
	float fvolume = volume * 0.001;

	if(volume>=1000) return;
	
	if(volume<=0) {
		memset(buffer,0,bufferSize);
		return;
	}

/* DEBUG */
	if(bufferSize % (format->channels * 4)) {
		ERROR("pcm_volumeChange: bufferSize=%i not multipel of %i\n",
				bufferSize, format->channels * 4);
	}
	if(!format->floatSamples)
		ERROR("pcm_volumeChange: not floatSamples\n");
/* /DEBUG */
	
	if(format->floatSamples) {
		if(format->bits==32) {
			while(bufferSize) {
				*bufferFloat *= fvolume;
				bufferFloat++;
				bufferSize-=4;
			}
			return;
		}
		else {
			ERROR("%i bit float not supported by pcm_volumeChange!\n",
					format->bits);
			exit(EXIT_FAILURE);
		}
	}

	switch(format->bits) {
	case 32: 
		while(bufferSize) {
			double sample = (double)(*buffer32) * fvolume;
			*buffer32++ = rint(sample);
			bufferSize-=4;
		}
		break;
	case 16:
		while(bufferSize) {
			float sample = *buffer16 * fvolume;
			*buffer16++ = rintf(sample);
			bufferSize-=2;
		}
		break;
	case 8:
		while(bufferSize) {
			float sample = *buffer8 * fvolume;
			*buffer8++ = rintf(sample);
			bufferSize--;
		}
		break;
	default:
		ERROR("%i bits not supported by pcm_volumeChange!\n",
				format->bits);
		exit(EXIT_FAILURE);
	}
#endif
}

void pcm_add(char * buffer1, char * buffer2, size_t bufferSize1, 
		size_t bufferSize2, int vol1, int vol2, AudioFormat * format)
{
#ifdef MPD_FIXED_POINT
	mpd_sint16 * buffer16_1 = (mpd_sint16 *)buffer1;
	mpd_sint16 * buffer16_2 = (mpd_sint16 *)buffer2;
	mpd_sint32 sample;
	
	if(format->bits!=16 || format.channels!=2 || format->floatSamples) {
		ERROR("Only 16 bit stereo is supported in fixed point mode!\n");
		exit(EXIT_FAILURE);
	}

	while(bufferSize1 && bufferSize2) {
		sample = ((mpd_sint32)(*buffer16_1) * vol1 +
			(mpd_sint32)(*buffer16_2) * vol2) >> 10;
		*buffer16_1 = sample>32767 ? 32767 : 
			(sample<-32768 ? -32768 : sample); 
		bufferSize1 -= 2;
		bufferSize2 -= 2;
	}
	if(bufferSize2) memcpy(buffer16_1,buffer16_2,bufferSize2);
	return;
#else
/* DEBUG */
	if(bufferSize1 % (format->channels * 4)) {
		ERROR("pcm_add: bufferSize1=%i not multipel of %i\n",
				bufferSize1, format->channels * 4);
	}
	if(bufferSize2 % (format->channels * 4)) {
		ERROR("pcm_add: bufferSize2=%i not multipel of %i\n",
				bufferSize2, format->channels * 4);
	}
	if(!format->floatSamples)
		ERROR("pcm_add: not floatSamples\n");
/* /DEBUG */
	mpd_sint8 * buffer8_1 = (mpd_sint8 *)buffer1;
	mpd_sint8 * buffer8_2 = (mpd_sint8 *)buffer2;
	mpd_sint16 * buffer16_1 = (mpd_sint16 *)buffer1;
	mpd_sint16 * buffer16_2 = (mpd_sint16 *)buffer2;
	mpd_sint32 * buffer32_1 = (mpd_sint32 *)buffer1;
	mpd_sint32 * buffer32_2 = (mpd_sint32 *)buffer2;
	float * bufferFloat_1 = (float *)buffer1;
	float * bufferFloat_2 = (float *)buffer2;
	float fvol1 = vol1 * 0.001;
	float fvol2 = vol2 * 0.001;
	float sample;
	
	if(format->floatSamples) {
		/* 32 bit float */
		while(bufferSize1 && bufferSize2) {
			*bufferFloat_1 = fvol1*(*bufferFloat_1) + 
					fvol2*(*bufferFloat_2);
			bufferFloat_1++;
			bufferFloat_2++;
			bufferSize1-=4;
			bufferSize2-=4;
		}
		if(bufferSize2) memcpy(bufferFloat_1,bufferFloat_2,bufferSize2);
	}
	
	switch(format->bits) {
	case 32:
		while(bufferSize1 && bufferSize2) {
			sample = fvol1*(*buffer32_1) + fvol2*(*buffer32_2);
			if(sample>2147483647.0)	*buffer32_1 = 2147483647;
			else if(sample<-2147483647.0) *buffer32_1 = -2147483647;
			else *buffer32_1 = rintf(sample);
			bufferFloat_1++;
			bufferFloat_2++;
			bufferSize1-=4;
			bufferSize2-=4;
		}
		if(bufferSize2) memcpy(bufferFloat_1,bufferFloat_2,bufferSize2);
		break;
	case 16:
		while(bufferSize1 && bufferSize2) {
			sample = fvol1*(*buffer16_1) + fvol2*(*buffer16_2);
			*buffer16_1 = sample>32767.0 ? 32767 : 
					(sample<-32768.0 ? -32768 : 
					rintf(sample));
			buffer16_1++;
			buffer16_2++;
			bufferSize1-=2;
			bufferSize2-=2;
		}
		if(bufferSize2) memcpy(buffer16_1,buffer16_2,bufferSize2);
		break;
	case 8:
		while(bufferSize1 && bufferSize2) {
			sample = fvol1*(*buffer8_1) + fvol2*(*buffer8_2);
			*buffer8_1 = sample>127.0 ? 127 : 
					(sample<-128.0 ? -128 : 
					rintf(sample));
			buffer8_1++;
			buffer8_2++;
			bufferSize1--;
			bufferSize2--;
		}
		if(bufferSize2) memcpy(buffer8_1,buffer8_2,bufferSize2);
		break;
	default:
		ERROR("%i bits not supported by pcm_add!\n",format->bits);
		exit(EXIT_FAILURE);
	}
#endif
}

void pcm_mix(char * buffer1, char * buffer2, size_t bufferSize1, 
		size_t bufferSize2, AudioFormat * format, float portion1)
{
	int vol1;
	float s = sin(M_PI_2*portion1);
	s*=s;
	
	vol1 = s*1000+0.5;
	vol1 = vol1>1000 ? 1000 : ( vol1<0 ? 0 : vol1 );

	pcm_add(buffer1,buffer2,bufferSize1,bufferSize2,vol1,1000-vol1,format);
}

void pcm_convertToFloat(AudioFormat * inFormat, char * inBuffer, size_t 
		samples, char * outBuffer)
{
	mpd_sint8 * in8 = (mpd_sint8 *)inBuffer;
	mpd_sint16 * in16 = (mpd_sint16 *)inBuffer;
	mpd_sint32 * in32 = (mpd_sint32 *)inBuffer;
	float * out = (float *)outBuffer;
	float multiplier;
	
	switch(inFormat->bits) {
	case 8:
		multiplier = 1.0 / 128.0;
		while(samples--) {
			*out++ = (*in8++) * multiplier;
		}
		break;
	case 16:
		multiplier = 1.0 / 32768.0;
		while(samples--) {
			*out++ = (*in16++) * multiplier;
		}
		break;
/*	case 32:
		multiplier = 1.0 / (1L << 31);
		while(samples--) {
			*out++ = (*in32++) * multiplier;
		}
		break; */
	default:
		ERROR("%i bit samples are not supported for conversion!\n",
				inFormat->bits);
		exit(EXIT_FAILURE);
	}
}


char *pcm_convertSampleRate(AudioFormat *inFormat, char *inBuffer, size_t inFrames, 
		AudioFormat *outFormat, size_t outFrames) 
{
	/* Input must be float32, 1 or 2 channels */ 
	/* Interpolate using a second order polynom */
	/* k0 = s0			*
	 * k2 = (s0 - 2*s1 + s2) * 0.5	*
	 * k1 = s1 - s0 - k2		*
	 * s[t] = k0 + k1*t +k2*t*t	*/

	static float * sampleConvBuffer = NULL;
	static int sampleConvBufferLength = 0;
	size_t dataSampleLen = 0;
	
	float *out;
	float *in = (float *)inBuffer;
	
	static float shift;
	static float offset;
	static float sample0l;
	static float sample0r;
	static float sample1l;
	static float sample1r;

	static int rateCheck = 0;
	static time_t timeCheck = 0;
	size_t c_rate = inFormat->sampleRate + inFormat->channels;
	time_t c_time = time(NULL);
	
	/* reset static data if changed samplerate ...*/
	if(c_rate != rateCheck || c_time != timeCheck) {
		ERROR("reset resampling\n",c_rate, rateCheck);
		rateCheck = c_rate;
		shift = (float)inFrames / (float)outFrames;
		offset = 1.5;
		sample0l = 0.0;
		sample0r = 0.0;
		sample1l = 0.0;
		sample1r = 0.0;
	}
	else { 
		/* ... otherwise check that shift is within bounds */ 
		float s = offset + (outFrames * shift) - inFrames;
		if(s > 1.5) {
			shift = (1.5-offset+(float)inFrames) / (float)outFrames;
		}
		else if(s < 0.5) {
			shift = (0.5-offset+(float)inFrames) / (float)outFrames;
		}
	}
	timeCheck = c_time;

	/* allocate data */
	dataSampleLen = 8 * outFrames;
	if(dataSampleLen > sampleConvBufferLength) {
		sampleConvBuffer = (float *)realloc(sampleConvBuffer,dataSampleLen);
		sampleConvBufferLength = dataSampleLen;
	}
	out = sampleConvBuffer;
	

	/* convert */
	switch(outFormat->channels) {
	case 1:	
	{
		float sample2l;
		float k0l, k1l, k2l;
		while(inFrames--) {
			sample2l = *in++;
			/* set coefficients */
			k0l = sample0l;
			k2l = (sample0l - 2.0 * sample1l + sample2l) * 0.5;
			k1l = sample1l - sample0l - k2l;
			/* calculate sample(s) */
			while(offset <= 1.5 && outFrames--) {
				*out++ = k0l + k1l*offset + k2l*offset*offset;
				offset += shift;
			}
			/* prepare for next frame */
			sample0l = sample1l;
			sample1l = sample2l;
			offset -= 1.0;
		}
	
		/* fill the last frames */
		while(outFrames--) {
			*out++ = k0l + k1l*offset + k2l*offset*offset;
			offset += shift;
		}
	}		
	break;
	case 2:	
	{
		float *out = sampleConvBuffer;
		float *in = (float *)inBuffer;
		float sample2l;
		float sample2r;
		float k0l, k1l, k2l;
		float k0r, k1r, k2r;
		while(inFrames--) {
			sample2l = *in++;
			sample2r = *in++;
			/* set coefficients */
			k0l = sample0l;
			k0r = sample0r;
			k2l = (sample0l - 2.0 * sample1l + sample2l) * 0.5;
			k2r = (sample0r - 2.0 * sample1r + sample2r) * 0.5;
			k1l = sample1l - sample0l - k2l;
			k1r = sample1r - sample0r - k2r;
			/* calculate sample(s) */
			while(offset <= 1.5 && outFrames--) {
			if(offset<0.5)
				ERROR("offset to small in resampling - %f\n",offset);
				*out++ = k0l + k1l*offset + k2l*offset*offset;
				*out++ = k0r + k1r*offset + k2r*offset*offset;
				offset += shift;
			}
			/* prepare for next frame */
			sample0l = sample1l;
			sample0r = sample1r;
			sample1l = sample2l;
			sample1r = sample2r;
			offset -= 1.0;
		}
	
		/* fill the last frame(s) */
		while(outFrames--) {
			if(offset>2.0)
				ERROR("offset to big in resampling - %f\n",offset);
			*out++ = k0l + k1l*offset + k2l*offset*offset;
			*out++ = k0r+ k1r*offset + k2r*offset*offset;
			offset += shift;
		}
	}		
	break;
	}
	return (char *)sampleConvBuffer;
}


/* now support conversions between sint8, sint16, sint32 and float32, 
 * 1 or 2 channels and (although badly) all sample rates */
void pcm_convertAudioFormat(AudioFormat * inFormat, char * inBuffer, size_t
		inSize, AudioFormat * outFormat, char * outBuffer)
{
#ifdef MPD_FIXED_POINT
	/* In fixed mode the conversion support is limited... */ 
	if(inFormat->bits != outFormat->bits || inFormat->bits != 16) {
		ERROR("Only 16 bit samples supported in fixed point mode!\n");
		exit(EXIT_FAILURE);
	}
	if(inFormat->sampleRate || outFormat->sampleRate) {
		ERROR("Sample rate conversions not supported in fixed point mode!\n");
		exit(EXIT_FAILURE);
	}
	if(inFormat->channels == 2 && outFormat->channels == 1) {
		size_t frames = inSize >> 2; /* 16 bit stereo == 4 bytes */
		mpd_sint16 *in = (mpd_sint16 *)inBuffer;
		mpd_sint16 *out = (mpd_sint16 *)outBuffer;
		while(frames--) {
			*out++ = *in++;
			*in++; /* skip the other channel */
		}
	}
	if(inFormat->channels == 1 && outFormat->channels == 2) {
		size_t frames = inSize >> 1; /* 16 bit mono == 2 bytes */
		mpd_sint16 *in = (mpd_sint16 *)inBuffer;
		mpd_sint16 *out = (mpd_sint16 *)outBuffer;
		while(frames--) {
			*out++ = *in;
			*out++ = *in++; /* duplicate the channel */
		}
	}
	else {
		ERROR("More then 2 channels are not supported!\n");
		exit(EXIT_FAILURE);
	}
	return;
	
#else 
/* DEBUG */
	if(inSize % (inFormat->channels * 4)) {
		ERROR("pcm_convertAudioFormat: inSize=%i not multipel of %i\n",
				inSize, inFormat->channels * 4);
	}
/* /DEBUG */

	static char *convBuffer = NULL;
	static int convBufferLength = 0;
	char * dataConv;
	int dataLen;
	static float ditherAmount = 2.0 / RAND_MAX;
	const size_t inSamples = (inSize << 3) / inFormat->bits;
	const size_t inFrames = inSamples / inFormat->channels;
	const size_t outFrames = (inFrames * (mpd_uint32)(outFormat->sampleRate)) / 
			inFormat->sampleRate;
	const size_t outSamples = outFrames * outFormat->channels;
		
	/* make sure convBuffer is big enough for 2 channels of float samples */
	dataLen = inFrames << 3;
	if(dataLen > convBufferLength) {
		convBuffer = (char *) realloc(convBuffer, dataLen);
		if(!convBuffer)
		{
			ERROR("Could not allocate more memory for convBuffer!\n");
			exit(EXIT_FAILURE);
		}
		convBufferLength = dataLen;
	}

	/* make sure dataConv points to 32 bit float samples */
	if(inFormat->floatSamples && inFormat->bits==32) {
		dataConv = inBuffer;
	}
	else if(!inFormat->floatSamples) {
		dataConv = convBuffer;
		pcm_convertToFloat(inFormat, inBuffer, inSamples, dataConv);
	}
	else {
		ERROR("%i bit float are not supported for conversion!\n", 
				inFormat->bits);
		exit(EXIT_FAILURE);
	}
	
	/* convert between mono and stereo samples*/
	if(inFormat->channels != outFormat->channels) {
		float *in = ((float *)dataConv)+inFrames;
		switch(inFormat->channels) {
		/* convert from 1 -> 2 channels */
		case 1:
			{
			float *out = ((float *)convBuffer)+(inFrames<<1);
			int f = inFrames;
			while(f--) {
				*out-- = *in;
				*out-- = *in--;
			}
			}
			break;
		/* convert from 2 -> 1 channels */
		case 2:
			{
			float * out = (float *)convBuffer;
			int f = inFrames;
			while(f--) {
				*out = (*in++)*0.5;
				*out++ += (*in++)*0.5;
			}
			}
			break;
		default:
			ERROR("only 1 or 2 channels are supported for conversion!\n");
			exit(EXIT_FAILURE);
		}
		dataConv = convBuffer;
	}

	/* convert sample rate */
	if(inFormat->sampleRate != outFormat->sampleRate) {
		dataConv = pcm_convertSampleRate(
				inFormat, dataConv, inFrames, 
				outFormat, outFrames);
	}

	/* convert to output format */
	if(outFormat->floatSamples && outFormat->bits==32) {
		if(outBuffer != dataConv)
			memcpy(outBuffer, dataConv, outSamples << 2);
		return;
	}
	else if(outFormat->floatSamples) {
		ERROR("%i bit float are not supported for conversion!\n", 
				outFormat->bits);
		exit(EXIT_FAILURE);
	}
		
	switch(outFormat->bits) {
	case 8:
		{
		/* add triangular dither and convert to sint8 */
		float * in = (float *)dataConv;
		mpd_sint8 * out = (mpd_sint8 *)outBuffer;
		int s = outSamples;
		while(s--) {
			float sample = (*in++) * 128.0 + 
				ditherAmount*(rand()-rand());
			*out++ = sample>127.0 ? 127 :
				(sample<-128.0 ? -128 : 
				 rintf(sample));
		}
		}
		break;
	case 16:
		{
		/* add triangular dither and convert to sint16 */
		float * in = (float *)dataConv;
		mpd_sint16 * out = (mpd_sint16 *)outBuffer;
		int s = outSamples;
		while(s--) {
			float sample = (*in++) * 32766.0 + 
				ditherAmount*(rand()-rand());
			*out++ = sample>32767.0 ? 32767 :
				(sample<-32768.0 ? -32768 :
				 rintf(sample));
		}
		}
		break;
	case 32:
		{
		/* convert to sint32 */
		float * in = (float *)dataConv;
		mpd_sint32 * out = (mpd_sint32 *)outBuffer;
		int s = outSamples;
		while(s--) {
			float sample = (*in++) * 2147483647.0;
			if(sample>2147483647.0)	*out++ = 2147483647;
			else if(sample<-2147483647.0) *out++ = -2147483647;
			else *out++ = rintf(sample);
		}
		}
		break;
	case 24: /* how do we store 24 bit? Maybe sint32 is all we need? */ 
	default:
		ERROR("%i bits are not supported for conversion!\n", outFormat->bits);
		exit(EXIT_FAILURE);
	}

	return;
#endif
}

size_t pcm_sizeOfOutputBufferForAudioFormatConversion(AudioFormat * inFormat,
		size_t inSize, AudioFormat * outFormat)
{
/* DEBUG */
	if(inSize % (inFormat->channels * 4)) {
		ERROR("pcm_sizeOOBFAFC: inSize=%i not multipel of %i\n",
				inSize, inFormat->channels * 4);
	}
/* /DEBUG */
	const int inShift = (inFormat->bits * inFormat->channels) >> 3;
	const int outShift = (outFormat->bits * outFormat->channels) >> 3;
	
	size_t inFrames = inSize / inShift;
	size_t outFrames = (inFrames * (mpd_uint32)(outFormat->sampleRate)) / 
			inFormat->sampleRate;
	
	size_t outSize = outFrames * outShift;
	
	return outSize;
}