A small hack to enable libmikmod to play 2-channel PCM files

Discuss the development of new homebrew software, tools and libraries.

Moderators: cheriff, TyRaNiD

Post Reply
dr_watson
Posts: 42
Joined: Mon Nov 28, 2005 11:30 am

A small hack to enable libmikmod to play 2-channel PCM files

Post by dr_watson »

libmikmod (or mikmodlib) is pretty good but it only supports mono PCM (Wav) files at the moment. You'll get a NULL pointer if trying to load a Wav file with 2 channels. Here is a small hack to make it load and play 2-channel Wav files. Technically speaking, this is not REAL stereo because, libmikmod somehow mixes the 2 channels together. But still, it's better than just having the sound effect played in your left ear only ;)

All we need to do is just to modify few lines in mwav.c as shown below (marked with @_@):

Code: Select all


SAMPLE* Sample_LoadGeneric_internal(MREADER* reader)
{
	SAMPLE *si=NULL;
	WAV wh;
	BOOL have_fmt=0;

	/* read wav header */
	_mm_read_string(wh.rID,4,reader);
	wh.rLen = _mm_read_I_ULONG(reader);
	_mm_read_string(wh.wID,4,reader);

	/* check for correct header */
	if(_mm_eof(reader)|| memcmp(wh.rID,"RIFF",4) || memcmp(wh.wID,"WAVE",4)) {
		_mm_errno = MMERR_UNKNOWN_WAVE_TYPE;
		return NULL;
	}

	/* scan all RIFF blocks until we find the sample data */
	for(;;) {
		CHAR dID[4];
		ULONG len,start;

		_mm_read_string(dID,4,reader);
		len = _mm_read_I_ULONG(reader);
		/* truncated file ? */
		if (_mm_eof(reader)) {
			_mm_errno=MMERR_UNKNOWN_WAVE_TYPE;
			return NULL;
		}
		start = _mm_ftell(reader);

		/* sample format block
		   should be present only once and before a data block */
		if(!memcmp(dID,"fmt ",4)) {
			wh.wFormatTag      = _mm_read_I_UWORD(reader);
			wh.nChannels       = _mm_read_I_UWORD(reader);
			wh.nSamplesPerSec  = _mm_read_I_ULONG(reader);
			wh.nAvgBytesPerSec = _mm_read_I_ULONG(reader);
			wh.nBlockAlign     = _mm_read_I_UWORD(reader);
			wh.nFormatSpecific = _mm_read_I_UWORD(reader);

#ifdef MIKMOD_DEBUG
			fprintf(stderr,"\rwavloader : wFormatTag=%04x blockalign=%04x nFormatSpc=%04x\n",
			        wh.wFormatTag,wh.nBlockAlign,wh.nFormatSpecific);
#endif

			if((have_fmt)||(wh.nChannels>2)) {				// @_@: changed 1 to 2
				_mm_errno=MMERR_UNKNOWN_WAVE_TYPE;
				return NULL;
			}
			have_fmt=1;
		} else
		/* sample data block
		   should be present only once and after a format block */
		  if(!memcmp(dID,"data",4)) {

			  int samp_size, num_samp;						// @_@: newly added variables

			if(!have_fmt) {
				_mm_errno=MMERR_UNKNOWN_WAVE_TYPE;
				return NULL;
			}
			if(!(si=(SAMPLE*)_mm_malloc(sizeof(SAMPLE)))) return NULL;
			si->speed  = wh.nSamplesPerSec*wh.nChannels;	// @_@: changed from (/) to (*)
			si->volume = 64;
			si->length = len;

			samp_size = 1;
			if((wh.nBlockAlign/wh.nChannels) == 2) {			// @_@: take into account the channel as well
				si->flags    = SF_16BITS | SF_SIGNED;
				//si->length >>= 1;								// @_@: keep the size
				samp_size = 2;									// @_@: 16 bit sample
			}

			if (wh.nChannels == 2)								// @_@: stereo
				si->flags |= SF_STEREO;

			num_samp = si->length/samp_size/wh.nChannels;		// @_@: setting up proper values for si
			si->loopstart=0;
			si->length=num_samp;
			si->loopend=num_samp;
			si->panning = PAN_CENTER;							// @_@: this one is the most important one!
																//		was not initialized in the original code so
																//		it default to PAN_LEFT

			si->inflags = si->flags;
			SL_RegisterSample(si,MD_SNDFX,reader);
			SL_LoadSamples();
			
			/* skip any other remaining blocks - so in case of repeated sample
			   fragments, we'll return the first anyway instead of an error */
			break;
		}
		/* onto next block */
		_mm_fseek(reader,start+len,SEEK_SET);
		if (_mm_eof(reader))
			break;
	}

	return si;
}

I believe you can do the same hack to mikmodlib as well.

Cheers!
User avatar
bpoint
Posts: 24
Joined: Thu Mar 10, 2005 4:35 pm
Location: Okinawa, Japan

Post by bpoint »

Your hack does not do what you think it does. libmikmod does not "somehow" mix the two channels together -- you simply set the playback sample rate to twice the speed of the original sample:

Code: Select all

si->speed  = wh.nSamplesPerSec*wh.nChannels;   // @_@: changed from (/) to (*)
This means a 44100hz sample will be played back at 88200hz, effectively skipping every other sample -- which happens to be the right channel since the data is interleaved. So all your patch is doing is allowing libmikmod to load a stereo sample and play back only the left channel.

If you use this hack and try to change the sample rate to anything other than the default, the skipped right channel will be intermixed with the left and will sound very strange.
dr_watson
Posts: 42
Joined: Mon Nov 28, 2005 11:30 am

Post by dr_watson »

bpoint wrote:Your hack does not do what you think it does. libmikmod does not "somehow" mix the two channels together -- you simply set the playback sample rate to twice the speed of the original sample:

Code: Select all

si->speed  = wh.nSamplesPerSec*wh.nChannels;   // @_@: changed from (/) to (*)
This means a 44100hz sample will be played back at 88200hz, effectively skipping every other sample -- which happens to be the right channel since the data is interleaved. So all your patch is doing is allowing libmikmod to load a stereo sample and play back only the left channel.

If you use this hack and try to change the sample rate to anything other than the default, the skipped right channel will be intermixed with the left and will sound very strange.
well... I'm afraid this is not 100% correct though. I tested the hack with a WAV file that has different sounds at left and right channel. When I played it, I could hear BOTH sounds at both ears. From your theory, I should have heard the sound from the left channel only. Any ideas?
User avatar
Raphael
Posts: 646
Joined: Tue Jan 17, 2006 4:54 pm
Location: Germany
Contact:

Post by Raphael »

When I played it, I could hear BOTH sounds at both ears. From your theory, I should have heard the sound from the left channel only. Any ideas?
Yes, because it doesn't skip samples when the sample rate is higher then the number of samples in the stream would suggest, it would only read the first half of the stream and output that at double speed. This then compensates for the half playback speed induced when playing double the number of channels without real stereo output (mono played back on stereo speakers will be output at both sides - it would not make sense to play a mono title only on one side, since mono doesn't mean only left or right, but rather "center"). So what you get with your hack is playing the left and right signals on both sides at double rate, thus creating your "mixing" (which only happens in your ears, not in libmikmod or the psp). If you want real stereo playback, you'd need to keep the si->speed at the normal speed (no / or *) and adjust the output functions of libmikmod to tell the psp that the incoming samples are indeed stereo (so the appropriate samples will be sent to the appropriate sides).

Regards,
Raphael
<Don't push the river, it flows.>
http://wordpress.fx-world.org - my devblog
http://wiki.fx-world.org - VFPU documentation wiki

Alexander Berl
Post Reply